diff --git a/.env.example b/.env.example index e257b32..6bd27c6 100644 --- a/.env.example +++ b/.env.example @@ -9,7 +9,7 @@ NEXT_PUBLIC_URL=http://localhost:3000 # Authentication AUTH_SECRET=NMKtKt29S8bYhbGRteHWurxlOSPXVg+DPswqNcZVZdA= -AUTH_TRUST_HOST=http://localhost:3000 +AUTH_URL=http://localhost:3000 # Encryption ENCRYPTION_KEY=WBNzRXdy/3vAdR0RBG+bkyY/PzFm9UWqp/E//J0nz9o= @@ -36,4 +36,4 @@ TRIGGER_SECRET_KEY= LANGSMITH_TRACING=false LANGSMITH_ENDPOINT=https://api.smith.langchain.com LANGSMITH_API_KEY= -LANGSMITH_PROJECT= \ No newline at end of file +LANGSMITH_PROJECT= diff --git a/Containerfile b/Containerfile index 85d6bcc..705dfbc 100644 --- a/Containerfile +++ b/Containerfile @@ -4,7 +4,7 @@ LABEL authors="OpenHealth" # Install coreutils for head RUN apk add coreutils -RUN apk add -U graphicsmagick +RUN apk add -U graphicsmagick ghostscript WORKDIR /app diff --git a/docker-compose.yaml b/docker-compose.yaml index 72ea94c..d219bae 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -10,10 +10,24 @@ services: - postgres_data:/var/lib/postgresql/data ports: - "5432:5432" + healthcheck: + test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER}"] + interval: 10s + timeout: 5s + retries: 5 + start_period: 10s docling-serve: image: ghcr.io/ds4sd/docling-serve:latest restart: always + ports: + - "5001:5001" + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:5001/health"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 40s app: build: @@ -22,8 +36,10 @@ services: args: - DATABASE_URL=postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@database:5432/${POSTGRES_DB} depends_on: - - database - - docling-serve + database: + condition: service_healthy + docling-serve: + condition: service_started volumes: - app_data:/app/public/uploads ports: @@ -31,6 +47,7 @@ services: restart: always environment: DATABASE_URL: postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@database:5432/${POSTGRES_DB} + DOCLING_URL: http://docling-serve:5001 volumes: postgres_data: diff --git a/package-lock.json b/package-lock.json index e3c5082..f78b56a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,7 +15,7 @@ "@langchain/core": "^0.3.39", "@langchain/google-genai": "^0.1.8", "@langchain/ollama": "^0.2.0", - "@prisma/client": "^6.4.1", + "@prisma/client": "^6.19.0", "@radix-ui/react-dialog": "^1.1.5", "@radix-ui/react-select": "^2.1.6", "@radix-ui/react-slot": "^1.1.1", @@ -71,7 +71,7 @@ "eslint": "^9", "eslint-config-next": "15.1.6", "postcss": "^8", - "prisma": "^6.4.1", + "prisma": "^6.19.0", "tailwindcss": "^3.4.1", "ts-node": "^10.9.2", "typescript": "^5" @@ -302,6 +302,7 @@ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.26.9.tgz", "integrity": "sha512-lWBYIrF7qK5+GjY5Uy+/hEgp8OJWOD/rpy74GplYRhEauvbHDeFB8t5hPOZxCZ0Oxf4Cc36tK51/l3ymJysrKw==", "license": "MIT", + "peer": true, "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.26.2", @@ -864,431 +865,6 @@ "integrity": "sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==", "license": "MIT" }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.0.tgz", - "integrity": "sha512-O7vun9Sf8DFjH2UtqK8Ku3LkquL9SZL8OLY1T5NZkA34+wG3OQF7cl4Ql8vdNzM6fzBbYfLaiRLIOZ+2FOCgBQ==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.0.tgz", - "integrity": "sha512-PTyWCYYiU0+1eJKmw21lWtC+d08JDZPQ5g+kFyxP0V+es6VPPSUhM6zk8iImp2jbV6GwjX4pap0JFbUQN65X1g==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.0.tgz", - "integrity": "sha512-grvv8WncGjDSyUBjN9yHXNt+cq0snxXbDxy5pJtzMKGmmpPxeAmAhWxXI+01lU5rwZomDgD3kJwulEnhTRUd6g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.0.tgz", - "integrity": "sha512-m/ix7SfKG5buCnxasr52+LI78SQ+wgdENi9CqyCXwjVR2X4Jkz+BpC3le3AoBPYTC9NHklwngVXvbJ9/Akhrfg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.0.tgz", - "integrity": "sha512-mVwdUb5SRkPayVadIOI78K7aAnPamoeFR2bT5nszFUZ9P8UpK4ratOdYbZZXYSqPKMHfS1wdHCJk1P1EZpRdvw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.0.tgz", - "integrity": "sha512-DgDaYsPWFTS4S3nWpFcMn/33ZZwAAeAFKNHNa1QN0rI4pUjgqf0f7ONmXf6d22tqTY+H9FNdgeaAa+YIFUn2Rg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.0.tgz", - "integrity": "sha512-VN4ocxy6dxefN1MepBx/iD1dH5K8qNtNe227I0mnTRjry8tj5MRk4zprLEdG8WPyAPb93/e4pSgi1SoHdgOa4w==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.0.tgz", - "integrity": "sha512-mrSgt7lCh07FY+hDD1TxiTyIHyttn6vnjesnPoVDNmDfOmggTLXRv8Id5fNZey1gl/V2dyVK1VXXqVsQIiAk+A==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.0.tgz", - "integrity": "sha512-vkB3IYj2IDo3g9xX7HqhPYxVkNQe8qTK55fraQyTzTX/fxaDtXiEnavv9geOsonh2Fd2RMB+i5cbhu2zMNWJwg==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.0.tgz", - "integrity": "sha512-9QAQjTWNDM/Vk2bgBl17yWuZxZNQIF0OUUuPZRKoDtqF2k4EtYbpyiG5/Dk7nqeK6kIJWPYldkOcBqjXjrUlmg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.0.tgz", - "integrity": "sha512-43ET5bHbphBegyeqLb7I1eYn2P/JYGNmzzdidq/w0T8E2SsYL1U6un2NFROFRg1JZLTzdCoRomg8Rvf9M6W6Gg==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.0.tgz", - "integrity": "sha512-fC95c/xyNFueMhClxJmeRIj2yrSMdDfmqJnyOY4ZqsALkDrrKJfIg5NTMSzVBr5YW1jf+l7/cndBfP3MSDpoHw==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.0.tgz", - "integrity": "sha512-nkAMFju7KDW73T1DdH7glcyIptm95a7Le8irTQNO/qtkoyypZAnjchQgooFUDQhNAy4iu08N79W4T4pMBwhPwQ==", - "cpu": [ - "mips64el" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.0.tgz", - "integrity": "sha512-NhyOejdhRGS8Iwv+KKR2zTq2PpysF9XqY+Zk77vQHqNbo/PwZCzB5/h7VGuREZm1fixhs4Q/qWRSi5zmAiO4Fw==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.0.tgz", - "integrity": "sha512-5S/rbP5OY+GHLC5qXp1y/Mx//e92L1YDqkiBbO9TQOvuFXM+iDqUNG5XopAnXoRH3FjIUDkeGcY1cgNvnXp/kA==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.0.tgz", - "integrity": "sha512-XM2BFsEBz0Fw37V0zU4CXfcfuACMrppsMFKdYY2WuTS3yi8O1nFOhil/xhKTmE1nPmVyvQJjJivgDT+xh8pXJA==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.0.tgz", - "integrity": "sha512-9yl91rHw/cpwMCNytUDxwj2XjFpxML0y9HAOH9pNVQDpQrBxHy01Dx+vaMu0N1CKa/RzBD2hB4u//nfc+Sd3Cw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-arm64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.0.tgz", - "integrity": "sha512-RuG4PSMPFfrkH6UwCAqBzauBWTygTvb1nxWasEJooGSJ/NwRw7b2HOwyRTQIU97Hq37l3npXoZGYMy3b3xYvPw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.0.tgz", - "integrity": "sha512-jl+qisSB5jk01N5f7sPCsBENCOlPiS/xptD5yxOx2oqQfyourJwIKLRA2yqWdifj3owQZCL2sn6o08dBzZGQzA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-arm64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.0.tgz", - "integrity": "sha512-21sUNbq2r84YE+SJDfaQRvdgznTD8Xc0oc3p3iW/a1EVWeNj/SdUCbm5U0itZPQYRuRTW20fPMWMpcrciH2EJw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.0.tgz", - "integrity": "sha512-2gwwriSMPcCFRlPlKx3zLQhfN/2WjJ2NSlg5TKLQOJdV0mSxIcYNTMhk3H3ulL/cak+Xj0lY1Ym9ysDV1igceg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.0.tgz", - "integrity": "sha512-bxI7ThgLzPrPz484/S9jLlvUAHYMzy6I0XiU1ZMeAEOBcS0VePBFxh1JjTQt3Xiat5b6Oh4x7UC7IwKQKIJRIg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.0.tgz", - "integrity": "sha512-ZUAc2YK6JW89xTbXvftxdnYy3m4iHIkDtK3CLce8wg8M2L+YZhIvO1DKpxrd0Yr59AeNNkTiic9YLf6FTtXWMw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.0.tgz", - "integrity": "sha512-eSNxISBu8XweVEWG31/JzjkIGbGIJN/TrRoiSVZwZ6pkC6VX4Im/WV2cz559/TXLcYbcrDN8JtKgd9DJVIo8GA==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.0.tgz", - "integrity": "sha512-ZENoHJBxA20C2zFzh6AI4fT6RraMzjYw4xKWemRTRmRVtN9c5DcH9r/f2ihEkMjOW5eGgrwCslG/+Y/3bL+DHQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, "node_modules/@eslint-community/eslint-utils": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.1.tgz", @@ -2099,6 +1675,7 @@ "resolved": "https://registry.npmjs.org/@langchain/anthropic/-/anthropic-0.3.13.tgz", "integrity": "sha512-2ZSr0bHVqkW5r4YeyL7zAyrb6I9oWt1uILs7Sw+sVInknCyxuS4mYJyr9/HCSG0g0EujFkFSeciG657Jtu/aTw==", "license": "MIT", + "peer": true, "dependencies": { "@anthropic-ai/sdk": "^0.36.3", "fast-xml-parser": "^4.4.1", @@ -2117,6 +1694,7 @@ "resolved": "https://registry.npmjs.org/@langchain/core/-/core-0.3.39.tgz", "integrity": "sha512-muXs4asy1A7qDtcdznxqyBfxf4N6qxofY/S0c95vbsWa0r9YAE2PttHIjcuxSy1q2jUiTkpCcgFEjNJRQRVhEw==", "license": "MIT", + "peer": true, "dependencies": { "@cfworker/json-schema": "^4.0.2", "ansi-styles": "^5.0.0", @@ -2140,6 +1718,7 @@ "resolved": "https://registry.npmjs.org/@langchain/google-genai/-/google-genai-0.1.8.tgz", "integrity": "sha512-aBz8IzEJfihOVz2GpqFrdLWjDlpscZUYIA7LZ2iazU7edO03etPaqxdexiYKr/Q+Be2D0BT7uaFJsouvQApIZA==", "license": "MIT", + "peer": true, "dependencies": { "@google/generative-ai": "^0.21.0", "zod-to-json-schema": "^3.22.4" @@ -2156,6 +1735,7 @@ "resolved": "https://registry.npmjs.org/@langchain/ollama/-/ollama-0.2.0.tgz", "integrity": "sha512-jLlYFqt+nbhaJKLakk7lRTWHZJ7wHeJLM6yuv4jToQ8zPzpL//372+MjggDoW0mnw8ofysg1T2C6mEJspKJtiA==", "license": "MIT", + "peer": true, "dependencies": { "ollama": "^0.5.12", "uuid": "^10.0.0", @@ -2578,6 +2158,7 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz", "integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==", "license": "Apache-2.0", + "peer": true, "engines": { "node": ">=8.0.0" } @@ -2964,9 +2545,9 @@ } }, "node_modules/@prisma/client": { - "version": "6.4.1", - "resolved": "https://registry.npmjs.org/@prisma/client/-/client-6.4.1.tgz", - "integrity": "sha512-A7Mwx44+GVZVexT5e2GF/WcKkEkNNKbgr059xpr5mn+oUm2ZW1svhe+0TRNBwCdzhfIZ+q23jEgsNPvKD9u+6g==", + "version": "6.19.0", + "resolved": "https://registry.npmjs.org/@prisma/client/-/client-6.19.0.tgz", + "integrity": "sha512-QXFT+N/bva/QI2qoXmjBzL7D6aliPffIwP+81AdTGq0FXDoLxLkWivGMawG8iM5B9BKfxLIXxfWWAF6wbuJU6g==", "hasInstallScript": true, "license": "Apache-2.0", "engines": { @@ -2985,54 +2566,67 @@ } } }, + "node_modules/@prisma/config": { + "version": "6.19.0", + "resolved": "https://registry.npmjs.org/@prisma/config/-/config-6.19.0.tgz", + "integrity": "sha512-zwCayme+NzI/WfrvFEtkFhhOaZb/hI+X8TTjzjJ252VbPxAl2hWHK5NMczmnG9sXck2lsXrxIZuK524E25UNmg==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "c12": "3.1.0", + "deepmerge-ts": "7.1.5", + "effect": "3.18.4", + "empathic": "2.0.0" + } + }, "node_modules/@prisma/debug": { - "version": "6.4.1", - "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-6.4.1.tgz", - "integrity": "sha512-Q9xk6yjEGIThjSD8zZegxd5tBRNHYd13GOIG0nLsanbTXATiPXCLyvlYEfvbR2ft6dlRsziQXfQGxAgv7zcMUA==", + "version": "6.19.0", + "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-6.19.0.tgz", + "integrity": "sha512-8hAdGG7JmxrzFcTzXZajlQCidX0XNkMJkpqtfbLV54wC6LSSX6Vni25W/G+nAANwLnZ2TmwkfIuWetA7jJxJFA==", "devOptional": true, "license": "Apache-2.0" }, "node_modules/@prisma/engines": { - "version": "6.4.1", - "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-6.4.1.tgz", - "integrity": "sha512-KldENzMHtKYwsOSLThghOIdXOBEsfDuGSrxAZjMnimBiDKd3AE4JQ+Kv+gBD/x77WoV9xIPf25GXMWffXZ17BA==", + "version": "6.19.0", + "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-6.19.0.tgz", + "integrity": "sha512-pMRJ+1S6NVdXoB8QJAPIGpKZevFjxhKt0paCkRDTZiczKb7F4yTgRP8M4JdVkpQwmaD4EoJf6qA+p61godDokw==", "devOptional": true, "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { - "@prisma/debug": "6.4.1", - "@prisma/engines-version": "6.4.0-29.a9055b89e58b4b5bfb59600785423b1db3d0e75d", - "@prisma/fetch-engine": "6.4.1", - "@prisma/get-platform": "6.4.1" + "@prisma/debug": "6.19.0", + "@prisma/engines-version": "6.19.0-26.2ba551f319ab1df4bc874a89965d8b3641056773", + "@prisma/fetch-engine": "6.19.0", + "@prisma/get-platform": "6.19.0" } }, "node_modules/@prisma/engines-version": { - "version": "6.4.0-29.a9055b89e58b4b5bfb59600785423b1db3d0e75d", - "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-6.4.0-29.a9055b89e58b4b5bfb59600785423b1db3d0e75d.tgz", - "integrity": "sha512-Xq54qw55vaCGrGgIJqyDwOq0TtjZPJEWsbQAHugk99hpDf2jcEeQhUcF+yzEsSqegBaDNLA4IC8Nn34sXmkiTQ==", + "version": "6.19.0-26.2ba551f319ab1df4bc874a89965d8b3641056773", + "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-6.19.0-26.2ba551f319ab1df4bc874a89965d8b3641056773.tgz", + "integrity": "sha512-gV7uOBQfAFlWDvPJdQxMT1aSRur3a0EkU/6cfbAC5isV67tKDWUrPauyaHNpB+wN1ebM4A9jn/f4gH+3iHSYSQ==", "devOptional": true, "license": "Apache-2.0" }, "node_modules/@prisma/fetch-engine": { - "version": "6.4.1", - "resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-6.4.1.tgz", - "integrity": "sha512-uZ5hVeTmDspx7KcaRCNoXmcReOD+84nwlO2oFvQPRQh9xiFYnnUKDz7l9bLxp8t4+25CsaNlgrgilXKSQwrIGQ==", + "version": "6.19.0", + "resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-6.19.0.tgz", + "integrity": "sha512-OOx2Lda0DGrZ1rodADT06ZGqHzr7HY7LNMaFE2Vp8dp146uJld58sRuasdX0OiwpHgl8SqDTUKHNUyzEq7pDdQ==", "devOptional": true, "license": "Apache-2.0", "dependencies": { - "@prisma/debug": "6.4.1", - "@prisma/engines-version": "6.4.0-29.a9055b89e58b4b5bfb59600785423b1db3d0e75d", - "@prisma/get-platform": "6.4.1" + "@prisma/debug": "6.19.0", + "@prisma/engines-version": "6.19.0-26.2ba551f319ab1df4bc874a89965d8b3641056773", + "@prisma/get-platform": "6.19.0" } }, "node_modules/@prisma/get-platform": { - "version": "6.4.1", - "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-6.4.1.tgz", - "integrity": "sha512-gXqZaDI5scDkBF8oza7fOD3Q3QMD0e0rBynlzDDZdTWbWmzjuW58PRZtj+jkvKje2+ZigCWkH8SsWZAsH6q1Yw==", + "version": "6.19.0", + "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-6.19.0.tgz", + "integrity": "sha512-ym85WDO2yDhC3fIXHWYpG3kVMBA49cL1XD2GCsCF8xbwoy2OkDQY44gEbAt2X46IQ4Apq9H6g0Ex1iFfPqEkHA==", "devOptional": true, "license": "Apache-2.0", "dependencies": { - "@prisma/debug": "6.4.1" + "@prisma/debug": "6.19.0" } }, "node_modules/@protobufjs/aspromise": { @@ -3703,6 +3297,13 @@ "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==", + "devOptional": true, + "license": "MIT" + }, "node_modules/@swc/counter": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz", @@ -4033,6 +3634,7 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.17.tgz", "integrity": "sha512-/WndGO4kIfMicEQLTi/mDANUu/iVUhT7KboZPdEqqHQ4aTS+3qT3U5gIqWDFV+XouorjfgGqvKILJeHhuQgFYg==", "license": "MIT", + "peer": true, "dependencies": { "undici-types": "~6.19.2" } @@ -4058,6 +3660,7 @@ "resolved": "https://registry.npmjs.org/@types/react/-/react-19.0.8.tgz", "integrity": "sha512-9P/o1IGdfmQxrujGbIMDyYaaCykhLKc0NGCtYcECNUr9UAaDe4gwvV9bR6tvd5Br1SG0j+PBpbKr2UYY8CwqSw==", "license": "MIT", + "peer": true, "dependencies": { "csstype": "^3.0.2" } @@ -4068,6 +3671,7 @@ "integrity": "sha512-0Knk+HJiMP/qOZgMyNFamlIjw9OFCsyC2ZbigmEEyXXixgre6IQpm/4V+r3qH4GC1JPvRJKInw+on2rV6YZLeA==", "devOptional": true, "license": "MIT", + "peer": true, "peerDependencies": { "@types/react": "^19.0.0" } @@ -4141,6 +3745,7 @@ "integrity": "sha512-h2lUByouOXFAlMec2mILeELUbME5SZRN/7R9Cw2RD2lRQQY08MWMM+PmVVKKJNK1aIwqTo9t/0CvOxwPbRIE2Q==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.23.0", "@typescript-eslint/types": "8.23.0", @@ -4380,6 +3985,7 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -4924,6 +4530,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "caniuse-lite": "^1.0.30001688", "electron-to-chromium": "^1.5.73", @@ -4973,6 +4580,94 @@ "node": ">=10.16.0" } }, + "node_modules/c12": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/c12/-/c12-3.1.0.tgz", + "integrity": "sha512-uWoS8OU1MEIsOv8p/5a82c3H31LsWVR5qiyXVfBNOzfffjUWtPnhAb4BYI2uG2HfGmZmFjCtui5XNWaps+iFuw==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "chokidar": "^4.0.3", + "confbox": "^0.2.2", + "defu": "^6.1.4", + "dotenv": "^16.6.1", + "exsolve": "^1.0.7", + "giget": "^2.0.0", + "jiti": "^2.4.2", + "ohash": "^2.0.11", + "pathe": "^2.0.3", + "perfect-debounce": "^1.0.0", + "pkg-types": "^2.2.0", + "rc9": "^2.1.2" + }, + "peerDependencies": { + "magicast": "^0.3.5" + }, + "peerDependenciesMeta": { + "magicast": { + "optional": true + } + } + }, + "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/confbox": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.2.tgz", + "integrity": "sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/c12/node_modules/jiti": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", + "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", + "devOptional": true, + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/c12/node_modules/pkg-types": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.3.0.tgz", + "integrity": "sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "confbox": "^0.2.2", + "exsolve": "^1.0.7", + "pathe": "^2.0.3" + } + }, + "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": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", @@ -5212,6 +4907,16 @@ "license": "ISC", "optional": true }, + "node_modules/citty": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/citty/-/citty-0.1.6.tgz", + "integrity": "sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "consola": "^3.2.3" + } + }, "node_modules/cjs-module-lexer": { "version": "1.4.3", "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.4.3.tgz", @@ -5476,6 +5181,16 @@ "dev": true, "license": "MIT" }, + "node_modules/consola": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/consola/-/consola-3.4.2.tgz", + "integrity": "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": "^14.18.0 || >=16.10.0" + } + }, "node_modules/console-table-printer": { "version": "2.12.1", "resolved": "https://registry.npmjs.org/console-table-printer/-/console-table-printer-2.12.1.tgz", @@ -5749,6 +5464,16 @@ "dev": true, "license": "MIT" }, + "node_modules/deepmerge-ts": { + "version": "7.1.5", + "resolved": "https://registry.npmjs.org/deepmerge-ts/-/deepmerge-ts-7.1.5.tgz", + "integrity": "sha512-HOJkrhaYsweh+W+e74Yn7YStZOilkoPb6fycpwNLKzSPtruFs48nYis0zy5yJz1+ktUhHxoRDJ27RQAWLIJVJw==", + "devOptional": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/defaults": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", @@ -5797,6 +5522,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/defu": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.4.tgz", + "integrity": "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==", + "devOptional": true, + "license": "MIT" + }, "node_modules/delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -5815,6 +5547,13 @@ "node": ">=6" } }, + "node_modules/destr": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/destr/-/destr-2.0.5.tgz", + "integrity": "sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==", + "devOptional": true, + "license": "MIT" + }, "node_modules/detect-libc": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz", @@ -5888,6 +5627,19 @@ "csstype": "^3.0.2" } }, + "node_modules/dotenv": { + "version": "16.6.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", + "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", + "devOptional": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", @@ -5909,6 +5661,17 @@ "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", "license": "MIT" }, + "node_modules/effect": { + "version": "3.18.4", + "resolved": "https://registry.npmjs.org/effect/-/effect-3.18.4.tgz", + "integrity": "sha512-b1LXQJLe9D11wfnOKAk3PKxuqYshQ0Heez+y5pnkd3jLj1yx9QhM72zZ9uUrOQyNvrs2GZZd/3maL0ZV18YuDA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.0.0", + "fast-check": "^3.23.1" + } + }, "node_modules/electron-to-chromium": { "version": "1.5.102", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.102.tgz", @@ -5921,6 +5684,16 @@ "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", "license": "MIT" }, + "node_modules/empathic": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/empathic/-/empathic-2.0.0.tgz", + "integrity": "sha512-i6UzDscO/XfAcNYD75CfICkmfLedpyPDdozrLMmQc5ORaQcdMoc21OnlEylMIqI7U8eniKrPMxxtj8k0vhmJhA==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=14" + } + }, "node_modules/end-of-stream": { "version": "1.4.4", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", @@ -6197,60 +5970,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/esbuild": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.0.tgz", - "integrity": "sha512-BXq5mqc8ltbaN34cDqWuYKyNhX8D/Z0J1xdtdQ8UcIIIyJyz+ZMKUt58tF3SrZ85jcfN/PZYhjR5uDQAYNVbuw==", - "devOptional": true, - "hasInstallScript": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.25.0", - "@esbuild/android-arm": "0.25.0", - "@esbuild/android-arm64": "0.25.0", - "@esbuild/android-x64": "0.25.0", - "@esbuild/darwin-arm64": "0.25.0", - "@esbuild/darwin-x64": "0.25.0", - "@esbuild/freebsd-arm64": "0.25.0", - "@esbuild/freebsd-x64": "0.25.0", - "@esbuild/linux-arm": "0.25.0", - "@esbuild/linux-arm64": "0.25.0", - "@esbuild/linux-ia32": "0.25.0", - "@esbuild/linux-loong64": "0.25.0", - "@esbuild/linux-mips64el": "0.25.0", - "@esbuild/linux-ppc64": "0.25.0", - "@esbuild/linux-riscv64": "0.25.0", - "@esbuild/linux-s390x": "0.25.0", - "@esbuild/linux-x64": "0.25.0", - "@esbuild/netbsd-arm64": "0.25.0", - "@esbuild/netbsd-x64": "0.25.0", - "@esbuild/openbsd-arm64": "0.25.0", - "@esbuild/openbsd-x64": "0.25.0", - "@esbuild/sunos-x64": "0.25.0", - "@esbuild/win32-arm64": "0.25.0", - "@esbuild/win32-ia32": "0.25.0", - "@esbuild/win32-x64": "0.25.0" - } - }, - "node_modules/esbuild-register": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/esbuild-register/-/esbuild-register-3.6.0.tgz", - "integrity": "sha512-H2/S7Pm8a9CL1uhp9OvjwrBh5Pvx0H8qVOxNu8Wed9Y7qv56MPtq+GGM8RJpq6glYJn9Wspr8uw7l55uyinNeg==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "debug": "^4.3.4" - }, - "peerDependencies": { - "esbuild": ">=0.12 <1" - } - }, "node_modules/escalade": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", @@ -6278,6 +5997,7 @@ "integrity": "sha512-aL4F8167Hg4IvsW89ejnpTwx+B/UQRzJPGgbIOl+4XqffWsahVVsLEWoZvnrVuwpWmnRd7XeXmQI1zlKcFDteA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.12.1", @@ -6482,6 +6202,7 @@ "integrity": "sha512-ixmkI62Rbc2/w8Vfxyh1jQRTdRTF52VxwRVHl/ykPAmqG+Nb7/kNn+byLP0LxPgI7zWA16Jt82SybJInmMia3A==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@rtsao/scc": "^1.1.0", "array-includes": "^3.1.8", @@ -6831,12 +6552,42 @@ "node": ">=6" } }, + "node_modules/exsolve": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.0.8.tgz", + "integrity": "sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==", + "devOptional": true, + "license": "MIT" + }, "node_modules/extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", "license": "MIT" }, + "node_modules/fast-check": { + "version": "3.23.2", + "resolved": "https://registry.npmjs.org/fast-check/-/fast-check-3.23.2.tgz", + "integrity": "sha512-h5+1OzzfCC3Ef7VbtKdcv7zsstUQwUDlYpUTvjeUsJAssPgLn7QzbboPtL5ro04Mq0rPOsMzl7q5hIbRs2wD1A==", + "devOptional": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ], + "license": "MIT", + "dependencies": { + "pure-rand": "^6.1.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -7326,6 +7077,24 @@ "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" } }, + "node_modules/giget": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/giget/-/giget-2.0.0.tgz", + "integrity": "sha512-L5bGsVkxJbJgdnwyuheIunkGatUF/zssUoxxjACCseZYAVbaqdh9Tsmmlkl8vYan09H7sbvKt4pS8GqKLBrEzA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "citty": "^0.1.6", + "consola": "^3.4.0", + "defu": "^6.1.4", + "node-fetch-native": "^1.6.6", + "nypm": "^0.6.0", + "pathe": "^2.0.3" + }, + "bin": { + "giget": "dist/cli.mjs" + } + }, "node_modules/github-from-package": { "version": "0.0.0", "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", @@ -8526,6 +8295,7 @@ "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz", "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", "license": "MIT", + "peer": true, "bin": { "jiti": "bin/jiti.js" } @@ -10172,6 +9942,7 @@ "resolved": "https://registry.npmjs.org/next/-/next-15.1.6.tgz", "integrity": "sha512-Hch4wzbaX0vKQtalpXvUiw5sYivBy4cm5rzUKrBnUB/y436LGrvOUqYvlSeNVCWFO/770gDlltR9gqZH62ct4Q==", "license": "MIT", + "peer": true, "dependencies": { "@next/env": "15.1.6", "@swc/counter": "0.1.3", @@ -10356,6 +10127,13 @@ } } }, + "node_modules/node-fetch-native": { + "version": "1.6.7", + "resolved": "https://registry.npmjs.org/node-fetch-native/-/node-fetch-native-1.6.7.tgz", + "integrity": "sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q==", + "devOptional": true, + "license": "MIT" + }, "node_modules/node-releases": { "version": "2.0.19", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", @@ -10398,6 +10176,45 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/nypm": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/nypm/-/nypm-0.6.2.tgz", + "integrity": "sha512-7eM+hpOtrKrBDCh7Ypu2lJ9Z7PNZBdi/8AT3AX8xoCj43BBVHD0hPSTEvMtkMpfs8FCqBGhxB+uToIQimA111g==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "citty": "^0.1.6", + "consola": "^3.4.2", + "pathe": "^2.0.3", + "pkg-types": "^2.3.0", + "tinyexec": "^1.0.1" + }, + "bin": { + "nypm": "dist/cli.mjs" + }, + "engines": { + "node": "^14.16.0 || >=16.10.0" + } + }, + "node_modules/nypm/node_modules/confbox": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.2.tgz", + "integrity": "sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/nypm/node_modules/pkg-types": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.3.0.tgz", + "integrity": "sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "confbox": "^0.2.2", + "exsolve": "^1.0.7", + "pathe": "^2.0.3" + } + }, "node_modules/oauth4webapi": { "version": "3.1.4", "resolved": "https://registry.npmjs.org/oauth4webapi/-/oauth4webapi-3.1.4.tgz", @@ -10537,6 +10354,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/ohash": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/ohash/-/ohash-2.0.11.tgz", + "integrity": "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==", + "devOptional": true, + "license": "MIT" + }, "node_modules/ollama": { "version": "0.5.13", "resolved": "https://registry.npmjs.org/ollama/-/ollama-0.5.13.tgz", @@ -10918,7 +10742,7 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/pdf2pic": { @@ -10962,6 +10786,13 @@ "url": "https://github.com/sponsors/Borewit" } }, + "node_modules/perfect-debounce": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-1.0.0.tgz", + "integrity": "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==", + "devOptional": true, + "license": "MIT" + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -11039,6 +10870,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "nanoid": "^3.3.8", "picocolors": "^1.1.1", @@ -11182,6 +11014,7 @@ "resolved": "https://registry.npmjs.org/preact/-/preact-10.11.3.tgz", "integrity": "sha512-eY93IVpod/zG3uMF22Unl8h9KkrcKIRs2EGar8hwLZZDU1lkjph303V9HZBwufh2s736U6VXuhD109LYqPoffg==", "license": "MIT", + "peer": true, "funding": { "type": "opencollective", "url": "https://opencollective.com/preact" @@ -11243,16 +11076,16 @@ "license": "MIT" }, "node_modules/prisma": { - "version": "6.4.1", - "resolved": "https://registry.npmjs.org/prisma/-/prisma-6.4.1.tgz", - "integrity": "sha512-q2uJkgXnua/jj66mk6P9bX/zgYJFI/jn4Yp0aS6SPRrjH/n6VyOV7RDe1vHD0DX8Aanx4MvgmUPPoYnR6MJnPg==", + "version": "6.19.0", + "resolved": "https://registry.npmjs.org/prisma/-/prisma-6.19.0.tgz", + "integrity": "sha512-F3eX7K+tWpkbhl3l4+VkFtrwJlLXbAM+f9jolgoUZbFcm1DgHZ4cq9AgVEgUym2au5Ad/TDLN8lg83D+M10ycw==", "devOptional": true, "hasInstallScript": true, "license": "Apache-2.0", + "peer": true, "dependencies": { - "@prisma/engines": "6.4.1", - "esbuild": ">=0.12 <1", - "esbuild-register": "3.6.0" + "@prisma/config": "6.19.0", + "@prisma/engines": "6.19.0" }, "bin": { "prisma": "build/index.js" @@ -11260,9 +11093,6 @@ "engines": { "node": ">=18.18" }, - "optionalDependencies": { - "fsevents": "2.3.3" - }, "peerDependencies": { "typescript": ">=5.1.0" }, @@ -11357,6 +11187,23 @@ "node": ">=6" } }, + "node_modules/pure-rand": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz", + "integrity": "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==", + "devOptional": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ], + "license": "MIT" + }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -11403,11 +11250,23 @@ "node": ">=0.10.0" } }, + "node_modules/rc9": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/rc9/-/rc9-2.1.2.tgz", + "integrity": "sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "defu": "^6.1.4", + "destr": "^2.0.3" + } + }, "node_modules/react": { "version": "19.0.0", "resolved": "https://registry.npmjs.org/react/-/react-19.0.0.tgz", "integrity": "sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ==", "license": "MIT", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -11417,6 +11276,7 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.0.0.tgz", "integrity": "sha512-4GV5sHFG0e/0AD4X+ySy6UJd3jVl1iNsNHdpad0qhABJ11twS3TTBnseqsKurKcsNqCEFeGL3uLpVChpIO3QfQ==", "license": "MIT", + "peer": true, "dependencies": { "scheduler": "^0.25.0" }, @@ -13183,6 +13043,7 @@ "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.17.tgz", "integrity": "sha512-w33E2aCvSDP0tW9RZuNXadXlkHXqFzSkQew/aIa2i/Sj8fThxwovwlXHSPXTbAHwEIhBFXAedUhP2tueAKP8Og==", "license": "MIT", + "peer": true, "dependencies": { "@alloc/quick-lru": "^5.2.0", "arg": "^5.0.2", @@ -13360,6 +13221,16 @@ "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==", "license": "MIT" }, + "node_modules/tinyexec": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.2.tgz", + "integrity": "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/tinyglobby": { "version": "0.2.11", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.11.tgz", @@ -13398,6 +13269,7 @@ "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -13495,6 +13367,7 @@ "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", "devOptional": true, "license": "MIT", + "peer": true, "dependencies": { "@cspotcode/source-map-support": "^0.8.0", "@tsconfig/node10": "^1.0.7", @@ -13708,6 +13581,7 @@ "integrity": "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==", "devOptional": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -14532,6 +14406,7 @@ "resolved": "https://registry.npmjs.org/zod/-/zod-3.24.1.tgz", "integrity": "sha512-muH7gBL9sI1nciMZV67X5fTKKBLtwpZ5VBp1vsOQzj1MhrBZ4wlVCm3gedKZWLp0Oyel8sIGfeiz54Su+OVT+A==", "license": "MIT", + "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } diff --git a/package.json b/package.json index 784cff7..24e5da7 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,7 @@ "@langchain/core": "^0.3.39", "@langchain/google-genai": "^0.1.8", "@langchain/ollama": "^0.2.0", - "@prisma/client": "^6.4.1", + "@prisma/client": "^6.19.0", "@radix-ui/react-dialog": "^1.1.5", "@radix-ui/react-select": "^2.1.6", "@radix-ui/react-slot": "^1.1.1", @@ -76,7 +76,7 @@ "eslint": "^9", "eslint-config-next": "15.1.6", "postcss": "^8", - "prisma": "^6.4.1", + "prisma": "^6.19.0", "tailwindcss": "^3.4.1", "ts-node": "^10.9.2", "typescript": "^5" diff --git a/prisma/seed.ts b/prisma/seed.ts index e845d3b..89dff2d 100644 --- a/prisma/seed.ts +++ b/prisma/seed.ts @@ -1,9 +1,55 @@ import {PrismaClient} from '@prisma/client' - +import {hash} from 'bcryptjs' +import assistantModeSeed from './data/assistant-mode.json' +import llmProviderSeed from './data/llm-provider.json' const prisma = new PrismaClient() async function main() { + // Create a default user for testing + const hashedPassword = await hash('password', 10) + + const user = await prisma.user.upsert({ + where: { username: 'demo' }, + update: {}, + create: { + username: 'demo', + password: hashedPassword, + hasOnboarded: true, + } + }) + + console.log(`Created/updated user: ${user.username}`) + + // Create assistant modes + for (const mode of assistantModeSeed) { + await prisma.assistantMode.upsert({ + where: { id: `${user.id}-${mode.name}` }, + update: {}, + create: { + id: `${user.id}-${mode.name}`, + ...mode, + authorId: user.id + } + }) + } + + console.log(`Created/updated ${assistantModeSeed.length} assistant modes`) + + // Create LLM providers + for (const provider of llmProviderSeed) { + await prisma.lLMProvider.upsert({ + where: { id: `${user.id}-${provider.providerId}` }, + update: {}, + create: { + id: `${user.id}-${provider.providerId}`, + ...provider, + authorId: user.id + } + }) + } + + console.log(`Created/updated ${llmProviderSeed.length} LLM providers`) } main() diff --git a/src/app/api/chat-rooms/[id]/messages/route.ts b/src/app/api/chat-rooms/[id]/messages/route.ts index 46be9a7..14529e3 100644 --- a/src/app/api/chat-rooms/[id]/messages/route.ts +++ b/src/app/api/chat-rooms/[id]/messages/route.ts @@ -1,7 +1,6 @@ import {NextRequest, NextResponse} from "next/server"; import prisma, {Prisma} from "@/lib/prisma"; import {auth} from "@/auth"; -import {decrypt} from "@/lib/encryption"; import {currentDeploymentEnv} from "@/lib/current-deployment-env"; import {ChatOpenAI} from "@langchain/openai"; import {ChatAnthropic} from "@langchain/anthropic"; @@ -90,7 +89,7 @@ export async function POST( let apiKey: string if (currentDeploymentEnv === 'local') { - apiKey = decrypt(llmProvider.apiKey) + apiKey = llmProvider.apiKey } else if (currentDeploymentEnv === 'cloud') { switch (llmProvider.providerId) { case 'openai': diff --git a/src/app/api/llm-providers/[id]/models/route.ts b/src/app/api/llm-providers/[id]/models/route.ts index 38fa72b..e944860 100644 --- a/src/app/api/llm-providers/[id]/models/route.ts +++ b/src/app/api/llm-providers/[id]/models/route.ts @@ -2,7 +2,6 @@ import {NextRequest, NextResponse} from "next/server"; import prisma from "@/lib/prisma"; import OpenAI from "openai"; import Anthropic from "@anthropic-ai/sdk"; -import {decrypt} from "@/lib/encryption"; import {currentDeploymentEnv} from "@/lib/current-deployment-env"; export interface LLMProviderModel { @@ -22,13 +21,7 @@ export async function GET( const llmProvider = await prisma.lLMProvider.findUniqueOrThrow({ where: {id} }); - let apiKey: string - try { - apiKey = decrypt(llmProvider.apiKey) - // eslint-disable-next-line @typescript-eslint/no-unused-vars - } catch (e) { - apiKey = '' - } + let apiKey: string = llmProvider.apiKey if (llmProvider.providerId === 'openai') { if (currentDeploymentEnv === 'cloud') apiKey = process.env.OPENAI_API_KEY as string diff --git a/src/app/api/llm-providers/[id]/route.ts b/src/app/api/llm-providers/[id]/route.ts index 0329a86..c1a9434 100644 --- a/src/app/api/llm-providers/[id]/route.ts +++ b/src/app/api/llm-providers/[id]/route.ts @@ -1,7 +1,6 @@ import {NextRequest, NextResponse} from "next/server"; import prisma from "@/lib/prisma"; import {LLMProvider} from "@/app/api/llm-providers/route"; -import {encrypt} from '@/lib/encryption' export interface LLMProviderPatchRequest { apiKey?: string @@ -18,9 +17,6 @@ export async function PATCH( const {id} = await params const body: LLMProviderPatchRequest = await req.json() - // API key encryption - if (body.apiKey) body.apiKey = encrypt(body.apiKey) - const llmProvider = await prisma.lLMProvider.update({ where: {id}, data: body diff --git a/src/app/api/llm-providers/route.ts b/src/app/api/llm-providers/route.ts index b3d194b..ea9fc24 100644 --- a/src/app/api/llm-providers/route.ts +++ b/src/app/api/llm-providers/route.ts @@ -1,7 +1,6 @@ import prisma, {Prisma} from "@/lib/prisma"; import {NextResponse} from "next/server"; import {auth} from "@/auth"; -import {decrypt} from "@/lib/encryption"; import {currentDeploymentEnv} from "@/lib/current-deployment-env"; export interface LLMProvider extends Prisma.LLMProviderGetPayload<{ @@ -14,6 +13,7 @@ export interface LLMProvider extends Prisma.LLMProviderGetPayload<{ } }> { id: string + apiKeyRequired?: boolean } export interface LLMProviderListResponse { @@ -30,6 +30,30 @@ export async function GET() { where: {authorId: session.user.id}, orderBy: {order: 'asc'} }) + + // Helper function to determine if API key is required + const isApiKeyRequired = (providerId: string): boolean => { + // Check if API key is available in environment variables + const envApiKeys: Record = { + 'openai': process.env.OPENAI_API_KEY, + 'anthropic': process.env.ANTHROPIC_API_KEY, + 'google': process.env.GOOGLE_API_KEY, + }; + + // If API key is available in environment, it's not required from user + if (envApiKeys[providerId]) { + return false; + } + + // Ollama doesn't require an API key + if (providerId === 'ollama') { + return false; + } + + // For other providers, API key is required if not in environment + return true; + }; + return NextResponse.json({ llmProviders: llmProviders .filter(({providerId}) => { @@ -37,16 +61,9 @@ export async function GET() { if (currentDeploymentEnv === 'cloud') return !['ollama'].includes(providerId) return true; }) - .map((provider) => { - let decryptedApiKey: string - try { - decryptedApiKey = decrypt(provider.apiKey) - // eslint-disable-next-line @typescript-eslint/no-unused-vars - } catch (e) { - decryptedApiKey = '' - } - - return {...provider, apiKey: decryptedApiKey} - }) + .map((provider) => ({ + ...provider, + apiKeyRequired: isApiKeyRequired(provider.providerId) + })) }) } diff --git a/src/app/chat/[id]/screen.tsx b/src/app/chat/[id]/screen.tsx index 09f9643..82eb8e8 100644 --- a/src/app/chat/[id]/screen.tsx +++ b/src/app/chat/[id]/screen.tsx @@ -26,7 +26,6 @@ export default function Screen( ) { const {id} = useParams<{ id: string }>(); const t = useTranslations('Chat') - const tf = useTranslations('Feedback') const messagesEndRef = useRef(null); @@ -108,11 +107,6 @@ export default function Screen( } }; - const handleCloseFeedbackBanner = () => { - setShowFeedbackBanner(false); - localStorage.setItem('feedbackBannerClosed', 'true'); - }; - return (
@@ -145,31 +139,6 @@ export default function Screen(
- {showFeedbackBanner && ( -
-
-

{tf('helpMessage')}

-
-
- - -
-
- )}
{ if (currentStep === 5) submit(); + // eslint-disable-next-line react-hooks/exhaustive-deps }, [currentStep]); const validateCurrentStep = () => { @@ -183,4 +184,4 @@ export default function OnboardingPage() {
); -} \ No newline at end of file +} diff --git a/src/components/chat/chat-setting-side-bar.tsx b/src/components/chat/chat-setting-side-bar.tsx index ea39cb5..449e34a 100644 --- a/src/components/chat/chat-setting-side-bar.tsx +++ b/src/components/chat/chat-setting-side-bar.tsx @@ -15,11 +15,11 @@ import {ConditionalDeploymentEnv} from "@/components/common/deployment-env"; import {useTranslations} from "next-intl"; interface ChatSettingSideBarProps { - isRightSidebarOpen: boolean; - chatRoomId: string; + readonly isRightSidebarOpen: boolean; + readonly chatRoomId: string; } -export default function ChatSettingSideBar({isRightSidebarOpen, chatRoomId}: ChatSettingSideBarProps +export default function ChatSettingSideBar({isRightSidebarOpen, chatRoomId}: Readonly ) { const t = useTranslations('ChatSettingSideBar') @@ -49,6 +49,7 @@ export default function ChatSettingSideBar({isRightSidebarOpen, chatRoomId}: Cha onChangeChatRoom({ llmProviderModelId: selectedLLMProviderModel?.id }) + // eslint-disable-next-line react-hooks/exhaustive-deps }, [selectedLLMProviderModel]); // Initialize assistant mode from localStorage or chatRoomData @@ -57,7 +58,7 @@ export default function ChatSettingSideBar({isRightSidebarOpen, chatRoomId}: Cha // If no saved prompt, use the current one and save it setSelectedAssistantMode(chatRoomData.chatRoom.assistantMode); - }, [chatRoomData?.chatRoom.assistantMode?.id]); + }, [chatRoomData?.chatRoom.assistantMode]); useEffect(() => { const chatRoom = chatRoomData?.chatRoom; @@ -73,7 +74,8 @@ export default function ChatSettingSideBar({isRightSidebarOpen, chatRoomId}: Cha if (selectedLLMProviderModel === undefined && models.length > 0) { setSelectedLLMProviderModel(models.find((model) => model.id === chatRoom.llmProviderModelId) || models[0]); } - }, [chatRoomData, llmProvidersData, llmProviderModels]); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [chatRoomData?.chatRoom, llmProvidersData?.llmProviders, llmProviderModels]); const { data: assistantModesData, @@ -194,7 +196,7 @@ export default function ChatSettingSideBar({isRightSidebarOpen, chatRoomId}: Cha - + {llmProvidersData?.llmProviders.map((provider) => {provider.name})} @@ -205,7 +207,7 @@ export default function ChatSettingSideBar({isRightSidebarOpen, chatRoomId}: Cha - + {llmProviderModels.map((model) => ( {model.name} @@ -227,7 +229,7 @@ export default function ChatSettingSideBar({isRightSidebarOpen, chatRoomId}: Cha onChange={(e) => onLLMProviderChange({apiURL: e.target.value})} /> )} - {selectedLLMProvider?.providerId !== 'ollama' && ( + {selectedLLMProvider?.providerId !== 'ollama' && selectedLLMProvider?.apiKeyRequired && (
)} + {selectedLLMProvider?.providerId !== 'ollama' && !selectedLLMProvider?.apiKeyRequired && ( +
+ {t('apiKeyConfigured')} +
+ )}
diff --git a/src/components/source/source-add-screen.tsx b/src/components/source/source-add-screen.tsx index f0013a1..43961e8 100644 --- a/src/components/source/source-add-screen.tsx +++ b/src/components/source/source-add-screen.tsx @@ -201,6 +201,8 @@ const AddSourceDialog: React.FC = ({ const [open, setOpen] = useState(false); const [showSettingsAlert, setShowSettingsAlert] = useState(false); const [uploadStatus, setUploadStatus] = useState(''); + const [isDragOver, setIsDragOver] = useState(false); + const fileInputRef = React.useRef(null); const handleFileUpload = async (e: ChangeEvent) => { if (!e.target.files?.length) return; @@ -222,6 +224,30 @@ const AddSourceDialog: React.FC = ({ } }; + const handleDragOver = (e: React.DragEvent) => { + e.preventDefault(); + e.stopPropagation(); + setIsDragOver(true); + }; + + const handleDragLeave = (e: React.DragEvent) => { + e.preventDefault(); + e.stopPropagation(); + setIsDragOver(false); + }; + + const handleDrop = (e: React.DragEvent) => { + e.preventDefault(); + e.stopPropagation(); + setIsDragOver(false); + + if (fileInputRef.current && e.dataTransfer.files) { + fileInputRef.current.files = e.dataTransfer.files; + const event = new Event('change', { bubbles: true }); + fileInputRef.current.dispatchEvent(event); + } + }; + const handleAddSymptoms = () => { const today = new Date().toISOString().split('T')[0]; onAddSymptoms(today); @@ -242,24 +268,33 @@ const AddSourceDialog: React.FC = ({ {t('addNewSource')}
- + +
{ switch (healthData.type) { @@ -608,7 +644,7 @@ const HealthDataPreview = ({healthData, formData, setFormData, setHealthData}: H }; } - }, [loading, focusedItem, getFocusedWords, ocr, userBloodTestResults?.test_result, allInputsBlurred, page]); + }, [loading, focusedItem, getFocusedWords, ocr, userBloodTestResults?.test_result, allInputsBlurred, page, currentPageTestResults, userBloodTestResultsPage]); useEffect(() => { document.querySelector('#test-result')?.scrollTo(0, 0); @@ -1208,28 +1244,28 @@ export default function SourceAddScreen() {

{t('title')}

-
-
+
+
0)} isSetUpDocumentParser={documentParser !== undefined && documentParserModel !== undefined && (!documentParserApiKeyRequired || documentParserApiKey.length > 0)} onFileUpload={handleFileUpload} onAddSymptoms={handleAddSymptoms}/> -
- {healthDataList?.healthDataList?.map((item) => ( - { - if (item.status === 'PARSING') return; - setSelectedId(item.id) - setFormData(item.data as Record) - }} - onDelete={handleDeleteSource} - /> - ))} -
+
+
+ {healthDataList?.healthDataList?.map((item) => ( + { + if (item.status === 'PARSING') return; + setSelectedId(item.id) + setFormData(item.data as Record) + }} + onDelete={handleDeleteSource} + /> + ))}
diff --git a/src/components/ui/nav-links.tsx b/src/components/ui/nav-links.tsx index 9910145..ae43bfc 100644 --- a/src/components/ui/nav-links.tsx +++ b/src/components/ui/nav-links.tsx @@ -3,14 +3,6 @@ import React from 'react'; export function NavLinks() { return ( ); -} \ No newline at end of file +} diff --git a/src/lib/health-data/parser/document/docling.ts b/src/lib/health-data/parser/document/docling.ts index f8ac353..5f6b438 100644 --- a/src/lib/health-data/parser/document/docling.ts +++ b/src/lib/health-data/parser/document/docling.ts @@ -9,9 +9,11 @@ import { } from "@/lib/health-data/parser/document/base-document"; import fetch from 'node-fetch' import FormData from 'form-data' -import fs from 'node:fs' import {currentDeploymentEnv} from "@/lib/current-deployment-env"; +const DOCLING_URL = process.env.DOCLING_URL || 'http://docling-serve:5001'; +const REQUEST_TIMEOUT_MS = 300000; // 5 minutes timeout for failure detection on multiple serial uploads + export class DoclingDocumentParser extends BaseDocumentParser { get apiKeyRequired(): boolean { return false; @@ -32,64 +34,136 @@ export class DoclingDocumentParser extends BaseDocumentParser { ]; } - async ocr(options: DocumentOCROptions): Promise { + private createFormData(fileBuffer: Buffer, filename: string, options: { + forceOcr?: boolean; + toFormat?: 'json' | 'md'; + } = {}): FormData { const formData = new FormData(); formData.append('ocr_engine', 'easyocr'); formData.append('pdf_backend', 'dlparse_v2'); formData.append('from_formats', 'pdf'); formData.append('from_formats', 'docx'); formData.append('from_formats', 'image'); - formData.append('force_ocr', 'false'); + formData.append('force_ocr', options.forceOcr ? 'true' : 'false'); formData.append('image_export_mode', 'placeholder'); formData.append('ocr_lang', 'en'); - formData.append('ocr_lang', 'ko'); formData.append('table_mode', 'fast'); - formData.append('files', fs.createReadStream(options.input)); + formData.append('files', fileBuffer, {filename: filename}); formData.append('abort_on_error', 'false'); - formData.append('to_formats', 'json'); + formData.append('to_formats', options.toFormat || 'json'); formData.append('return_as_file', 'false'); - formData.append('do_ocr', 'true'); - - const response = await fetch('http://docling-serve:5001/v1alpha/convert/file', { - method: 'POST', - headers: {'accept': 'application/json'}, - body: formData - }) - const data = await response.json() - const {document} = data - const {json_content} = document + formData.append('do_ocr', options.forceOcr ? 'true' : 'false'); + return formData; + } - const convertedJsonContent = this.convertJsonContent(json_content) + async ocr(options: DocumentOCROptions): Promise { + try { + console.log('[Docling OCR] Starting OCR for file:', options.input); + + // Fetch the file from the URL + const fileResponse = await fetch(options.input); + if (!fileResponse.ok) { + throw new Error(`Failed to fetch file: ${fileResponse.status} ${fileResponse.statusText}`); + } + const fileBuffer = await fileResponse.buffer(); + console.log('[Docling OCR] File fetched, size:', fileBuffer.length, 'bytes'); + + // Extract filename from URL + const urlPath = new URL(options.input).pathname; + const filename = urlPath.split('/').pop() || 'document.pdf'; + console.log('[Docling OCR] Using filename:', filename); + + const formData = this.createFormData(fileBuffer, filename, { + forceOcr: true, + toFormat: 'json' + }); - return {ocr: convertedJsonContent}; + console.log('[Docling OCR] Sending request to', DOCLING_URL); + + const response = await Promise.race([ + fetch(`${DOCLING_URL}/v1alpha/convert/file`, { + method: 'POST', + body: formData + }), + new Promise((_, reject) => + setTimeout(() => reject(new Error(`Docling request timeout after ${REQUEST_TIMEOUT_MS / 1000} seconds`)), REQUEST_TIMEOUT_MS) + ) + ]) as Response; + + console.log('[Docling OCR] Response received:', response.status); + + if (!response.ok) { + const errorText = await response.text(); + console.error('[Docling OCR] Error response:', errorText); + throw new Error(`Docling service error: ${response.status} ${response.statusText} - ${errorText}`) + } + + const data = await response.json() + const {document} = data + const {json_content} = document + + const convertedJsonContent = this.convertJsonContent(json_content) + console.log('[Docling OCR] OCR completed successfully'); + + return {ocr: convertedJsonContent}; + } catch (error) { + console.error('[Docling OCR] Error:', error) + throw new Error(`Failed to perform OCR with Docling: ${error instanceof Error ? error.message : String(error)}`) + } } async parse(options: DocumentParseOptions): Promise { - const formData = new FormData(); - formData.append('ocr_engine', 'easyocr'); - formData.append('pdf_backend', 'dlparse_v2'); - formData.append('from_formats', 'pdf'); - formData.append('from_formats', 'docx'); - formData.append('from_formats', 'image'); - formData.append('force_ocr', 'true'); - formData.append('image_export_mode', 'placeholder'); - formData.append('ocr_lang', 'en'); - formData.append('ocr_lang', 'ko'); - formData.append('table_mode', 'fast'); - formData.append('files', fs.createReadStream(options.input)); - formData.append('abort_on_error', 'false'); - formData.append('to_formats', 'md'); - formData.append('return_as_file', 'false'); - formData.append('do_ocr', 'true'); - - const response = await fetch('http://docling-serve:5001/v1alpha/convert/file', { - method: 'POST', - headers: {'accept': 'application/json'}, - body: formData - }) - const {document} = await response.json() - const {md_content} = document - return {document: {content: {markdown: md_content}}}; + try { + console.log('[Docling Parse] Starting parse for file:', options.input); + + // Fetch the file from the URL + const fileResponse = await fetch(options.input); + if (!fileResponse.ok) { + throw new Error(`Failed to fetch file: ${fileResponse.status} ${fileResponse.statusText}`); + } + const fileBuffer = await fileResponse.buffer(); + console.log('[Docling Parse] File fetched, size:', fileBuffer.length, 'bytes'); + + // Extract filename from URL + const urlPath = new URL(options.input).pathname; + const filename = urlPath.split('/').pop() || 'document.pdf'; + console.log('[Docling Parse] Using filename:', filename); + + // Optimization: Try without OCR first for faster processing + // If the PDF has selectable text, this will be much faster + const formData = this.createFormData(fileBuffer, filename, { + forceOcr: false, + toFormat: 'md' + }); + + console.log('[Docling Parse] Sending request to', DOCLING_URL); + + const response = await Promise.race([ + fetch(`${DOCLING_URL}/v1alpha/convert/file`, { + method: 'POST', + body: formData + }), + new Promise((_, reject) => + setTimeout(() => reject(new Error(`Docling request timeout after ${REQUEST_TIMEOUT_MS / 1000} seconds`)), REQUEST_TIMEOUT_MS) + ) + ]) as Response; + + console.log('[Docling Parse] Response received:', response.status); + + if (!response.ok) { + const errorText = await response.text(); + console.error('[Docling Parse] Error response:', errorText); + throw new Error(`Docling service error: ${response.status} ${response.statusText} - ${errorText}`) + } + + const {document} = await response.json() + const {md_content} = document + console.log('[Docling Parse] Parse completed successfully'); + return {document: {content: {markdown: md_content}}}; + } catch (error) { + console.error('[Docling Parse] Error:', error) + throw new Error(`Failed to parse document with Docling: ${error instanceof Error ? error.message : String(error)}`) + } } private convertCoordinates(bbox: { l: number; t: number; r: number; b: number }, pageHeight: number) { diff --git a/src/lib/health-data/parser/document/upstage.ts b/src/lib/health-data/parser/document/upstage.ts index e34fbd4..9ba51f6 100644 --- a/src/lib/health-data/parser/document/upstage.ts +++ b/src/lib/health-data/parser/document/upstage.ts @@ -18,7 +18,8 @@ export class UpstageDocumentParser extends BaseDocumentParser { } get apiKeyRequired(): boolean { - return currentDeploymentEnv === 'local' + // Only require API key if we're in local environment AND no key is provided in .env + return currentDeploymentEnv === 'local' && !process.env.UPSTAGE_API_KEY } get enabled(): boolean { @@ -41,7 +42,11 @@ export class UpstageDocumentParser extends BaseDocumentParser { formData.append("schema", "oac"); formData.append("model", "ocr-2.2.1"); - const apiKey = currentDeploymentEnv === 'cloud' ? process.env.UPSTAGE_API_KEY : options.apiKey + // Use environment variable if available, otherwise use provided API key + const apiKey = process.env.UPSTAGE_API_KEY || options.apiKey; + if (!apiKey) { + throw new Error('Upstage API key is required but not provided'); + } const response = await fetch('https://api.upstage.ai/v1/document-ai/ocr', { method: 'POST', body: formData, @@ -62,13 +67,34 @@ export class UpstageDocumentParser extends BaseDocumentParser { formData.append("coordinates", "true"); formData.append("model", "document-parse"); - const apiKey = currentDeploymentEnv === 'cloud' ? process.env.UPSTAGE_API_KEY : options.apiKey + // Use environment variable if available, otherwise use provided API key + const apiKey = process.env.UPSTAGE_API_KEY || options.apiKey; + if (!apiKey) { + throw new Error('Upstage API key is required but not provided'); + } const response = await fetch('https://api.upstage.ai/v1/document-ai/document-parse', { method: 'POST', body: formData, headers: {Authorization: `Bearer ${apiKey}`} }) - return {document: await response.json()} + if (!response.ok) { + const errorText = await response.text(); + throw new Error(`Upstage API error: ${response.status} ${response.statusText} - ${errorText}`); + } + + const responseData = await response.json(); + + // Extract markdown content from Upstage response + // Upstage returns: { "document": { "pages": [...], "metadata": {...} } } + // We need to extract the markdown content + let markdown = ''; + if (responseData.document?.pages) { + markdown = responseData.document.pages + .map((page: Record) => (page.content as string) || '') + .join('\n'); + } + + return {document: {content: {markdown}}} } } diff --git a/src/lib/health-data/parser/pdf.ts b/src/lib/health-data/parser/pdf.ts index 72105a8..998533d 100644 --- a/src/lib/health-data/parser/pdf.ts +++ b/src/lib/health-data/parser/pdf.ts @@ -91,9 +91,27 @@ async function inference(inferenceOptions: InferenceOptions) { const pageDataList: { page_content: string }[] | undefined = !excludeText ? await processBatchWithConcurrency( imagePaths, async (path) => { - const {content} = await documentParse({document: path, documentParser: documentParserOptions}) - const {markdown} = content - return {page_content: markdown} + try { + const result = await documentParse({document: path, documentParser: documentParserOptions}) + if (!result) { + console.warn('[Inference] Document parse returned undefined for path:', path) + return {page_content: ''} + } + const {content} = result + if (!content) { + console.warn('[Inference] Document parse returned no content for path:', path) + return {page_content: ''} + } + const {markdown} = content + if (!markdown) { + console.warn('[Inference] Document parse returned no markdown for path:', path) + return {page_content: ''} + } + return {page_content: markdown} + } catch (error) { + console.error('[Inference] Error parsing document:', error) + return {page_content: ''} + } }, 2 ) : undefined @@ -277,7 +295,7 @@ export async function parseHealthData(options: SourceParseOptions) { const visionParser = options.visionParser || { parser: 'OpenAI', model: 'gpt-4o', - apiKey: process.env.OPENAI_API as string + apiKey: process.env.OPENAI_API_KEY as string } // Document Parser diff --git a/src/lib/health-data/parser/vision/google.ts b/src/lib/health-data/parser/vision/google.ts index 850a712..0c69d0f 100644 --- a/src/lib/health-data/parser/vision/google.ts +++ b/src/lib/health-data/parser/vision/google.ts @@ -16,7 +16,8 @@ export class GoogleVisionParser extends BaseVisionParser { } get apiKeyRequired(): boolean { - return currentDeploymentEnv === 'local' + // Only require API key if we're in local environment AND no key is provided in .env + return currentDeploymentEnv === 'local' && !process.env.GOOGLE_API_KEY } get enabled(): boolean { @@ -34,9 +35,14 @@ export class GoogleVisionParser extends BaseVisionParser { } async parse(options: VisionParseOptions) { + // Use environment variable if available, otherwise use provided API key + const apiKey = process.env.GOOGLE_API_KEY || options.apiKey; + if (!apiKey) { + throw new Error('Google API key is required but not provided'); + } const llm = new ChatGoogleGenerativeAI({ model: options.model.id, - apiKey: currentDeploymentEnv === 'cloud' ? process.env.GOOGLE_API_KEY : options.apiKey, + apiKey: apiKey, }); const messages = options.messages || ChatPromptTemplate.fromMessages([]); diff --git a/src/lib/health-data/parser/vision/openai.ts b/src/lib/health-data/parser/vision/openai.ts index d294c25..0301b57 100644 --- a/src/lib/health-data/parser/vision/openai.ts +++ b/src/lib/health-data/parser/vision/openai.ts @@ -11,7 +11,8 @@ export class OpenAIVisionParser extends BaseVisionParser { } get apiKeyRequired(): boolean { - return currentDeploymentEnv === 'local' + // Only require API key if we're in local environment AND no key is provided in .env + return currentDeploymentEnv === 'local' && !process.env.OPENAI_API_KEY } get enabled(): boolean { @@ -33,9 +34,14 @@ export class OpenAIVisionParser extends BaseVisionParser { } async parse(options: VisionParseOptions) { + // Use environment variable if available, otherwise use provided API key + const apiKey = process.env.OPENAI_API_KEY || options.apiKey; + if (!apiKey) { + throw new Error('OpenAI API key is required but not provided'); + } const llm = new ChatOpenAI({ model: options.model.id, - apiKey: currentDeploymentEnv === 'cloud' ? process.env.OPENAI_API_KEY : options.apiKey + apiKey: apiKey }) const messages = options.messages || ChatPromptTemplate.fromMessages([]); const chain = messages.pipe(llm.withStructuredOutput(HealthCheckupSchema, { diff --git a/src/middleware.ts b/src/middleware.ts index 7c661fb..0b0451a 100644 --- a/src/middleware.ts +++ b/src/middleware.ts @@ -2,18 +2,14 @@ import {auth as middleware} from '@/auth'; const signInPathName = '/login'; -// eslint-disable-next-line @typescript-eslint/ban-ts-comment -// @ts-expect-error export default middleware((req) => { const {nextUrl} = req; const isAuthenticated = !!req.auth; // Prevent login redirect loop - if (nextUrl.pathname === signInPathName) return null; + if (nextUrl.pathname === signInPathName) return; if (!isAuthenticated) return Response.redirect(new URL(signInPathName, nextUrl)); - - return null; }) export const config = {