diff --git a/.github/workflows/docs-deploy-kubeconfig.yml b/.github/workflows/docs-deploy-kubeconfig.yml index 9d4be40d2ea1..cc6d11ca75d4 100644 --- a/.github/workflows/docs-deploy-kubeconfig.yml +++ b/.github/workflows/docs-deploy-kubeconfig.yml @@ -6,8 +6,6 @@ on: - 'docSite/**' branches: - 'main' - tags: - - 'v*.*.*' jobs: build-fastgpt-docs-images: diff --git a/.github/workflows/docs-deploy-vercel.yml b/.github/workflows/docs-deploy-vercel.yml index fed4a830b341..0b8b91b50c60 100644 --- a/.github/workflows/docs-deploy-vercel.yml +++ b/.github/workflows/docs-deploy-vercel.yml @@ -7,8 +7,6 @@ on: - 'docSite/**' branches: - 'main' - tags: - - 'v*.*.*' # A workflow run is made up of one or more jobs that can run sequentially or in parallel jobs: diff --git a/.github/workflows/docs-preview.yml b/.github/workflows/docs-preview.yml index a76242a3a16f..e66e5d64ed98 100644 --- a/.github/workflows/docs-preview.yml +++ b/.github/workflows/docs-preview.yml @@ -4,8 +4,6 @@ on: pull_request_target: paths: - 'docSite/**' - branches: - - 'main' workflow_dispatch: # A workflow run is made up of one or more jobs that can run sequentially or in parallel diff --git a/.github/workflows/sync_imgs.yml b/.github/workflows/docs-sync_imgs.yml similarity index 100% rename from .github/workflows/sync_imgs.yml rename to .github/workflows/docs-sync_imgs.yml diff --git a/.github/workflows/fastgpt-image-personal.yml b/.github/workflows/fastgpt-build-image-personal.yml similarity index 100% rename from .github/workflows/fastgpt-image-personal.yml rename to .github/workflows/fastgpt-build-image-personal.yml diff --git a/.github/workflows/fastgpt-image.yml b/.github/workflows/fastgpt-build-image.yml similarity index 99% rename from .github/workflows/fastgpt-image.yml rename to .github/workflows/fastgpt-build-image.yml index 3dc362c683af..ccd54859cbcf 100644 --- a/.github/workflows/fastgpt-image.yml +++ b/.github/workflows/fastgpt-build-image.yml @@ -26,7 +26,7 @@ jobs: with: driver-opts: network=host - name: Cache Docker layers - uses: actions/cache@v2 + uses: actions/cache@v4 with: path: /tmp/.buildx-cache key: ${{ runner.os }}-buildx-${{ github.sha }} @@ -108,7 +108,7 @@ jobs: with: driver-opts: network=host - name: Cache Docker layers - uses: actions/cache@v2 + uses: actions/cache@v4 with: path: /tmp/.buildx-cache key: ${{ runner.os }}-buildx-${{ github.sha }} @@ -191,7 +191,7 @@ jobs: with: driver-opts: network=host - name: Cache Docker layers - uses: actions/cache@v2 + uses: actions/cache@v4 with: path: /tmp/.buildx-cache key: ${{ runner.os }}-buildx-${{ github.sha }} diff --git a/.github/workflows/preview-fastgpt-image.yml b/.github/workflows/fastgpt-preview-image.yml similarity index 90% rename from .github/workflows/preview-fastgpt-image.yml rename to .github/workflows/fastgpt-preview-image.yml index 03d4263048ca..3fcaf1c1fbed 100644 --- a/.github/workflows/preview-fastgpt-image.yml +++ b/.github/workflows/fastgpt-preview-image.yml @@ -68,14 +68,3 @@ jobs: SEALOS_TYPE: 'pr_comment' SEALOS_FILENAME: 'report.md' SEALOS_REPLACE_TAG: 'DEFAULT_REPLACE_DEPLOY' - - helm-check: - runs-on: ubuntu-20.04 - steps: - - name: Checkout - uses: actions/checkout@v3 - - name: Helm Check - run: | - helm dependency update files/helm/fastgpt - helm lint files/helm/fastgpt - helm package files/helm/fastgpt diff --git a/.github/workflows/fastgpt-test.yaml b/.github/workflows/fastgpt-test.yaml new file mode 100644 index 000000000000..b494af720541 --- /dev/null +++ b/.github/workflows/fastgpt-test.yaml @@ -0,0 +1,28 @@ +name: 'FastGPT-Test' +on: + pull_request: + +jobs: + test: + runs-on: ubuntu-latest + + permissions: + # Required to checkout the code + contents: read + # Required to put a comment into the pull-request + pull-requests: write + + steps: + - uses: actions/checkout@v4 + - uses: pnpm/action-setup@v4 + with: + version: 10 + - name: 'Install Deps' + run: pnpm install + - name: 'Test' + run: pnpm run test:all + - name: 'Report Coverage' + # Set if: always() to also generate the report if tests are failing + # Only works if you set `reportOnFailure: true` in your vite config as specified above + if: always() + uses: davelosert/vitest-coverage-report-action@v2 diff --git a/.github/workflows/helm-release.yaml b/.github/workflows/helm-release.yaml index 8b58971704d5..25f7fb7713e8 100644 --- a/.github/workflows/helm-release.yaml +++ b/.github/workflows/helm-release.yaml @@ -24,6 +24,6 @@ jobs: export APP_VERSION=${{ steps.vars.outputs.tag }} export HELM_VERSION=${{ steps.vars.outputs.tag }} export HELM_REPO=ghcr.io/${{ github.repository_owner }} - helm dependency update files/helm/fastgpt - helm package files/helm/fastgpt --version ${HELM_VERSION}-helm --app-version ${APP_VERSION} -d bin + helm dependency update deploy/helm/fastgpt + helm package deploy/helm/fastgpt --version ${HELM_VERSION}-helm --app-version ${APP_VERSION} -d bin helm push bin/fastgpt-${HELM_VERSION}-helm.tgz oci://${HELM_REPO} diff --git a/.github/workflows/build-sandbox-image.yml b/.github/workflows/sandbox-build-image.yml similarity index 99% rename from .github/workflows/build-sandbox-image.yml rename to .github/workflows/sandbox-build-image.yml index a1a745a1f2fb..8e58daf46750 100644 --- a/.github/workflows/build-sandbox-image.yml +++ b/.github/workflows/sandbox-build-image.yml @@ -25,7 +25,7 @@ jobs: with: driver-opts: network=host - name: Cache Docker layers - uses: actions/cache@v2 + uses: actions/cache@v4 with: path: /tmp/.buildx-cache key: ${{ runner.os }}-buildx-${{ github.sha }} diff --git a/.gitignore b/.gitignore index 5be731c50562..ec9cb7ca92a1 100644 --- a/.gitignore +++ b/.gitignore @@ -44,3 +44,4 @@ files/helm/fastgpt/fastgpt-0.1.0.tgz files/helm/fastgpt/charts/*.tgz tmp/ +coverage diff --git a/.vscode/nextapi.code-snippets b/.vscode/nextapi.code-snippets index ddc53f3b8689..ca6bd28f3f89 100644 --- a/.vscode/nextapi.code-snippets +++ b/.vscode/nextapi.code-snippets @@ -58,7 +58,7 @@ "body": [ "import '@/pages/api/__mocks__/base';", "import { root } from '@/pages/api/__mocks__/db/init';", - "import { getTestRequest } from '@/test/utils';", + "import { getTestRequest } from '@fastgpt/service/test/utils'; ;", "import { AppErrEnum } from '@fastgpt/global/common/error/code/app';", "import handler from './demo';", "", diff --git a/.vscode/settings.json b/.vscode/settings.json index c674d0733827..bba595f12150 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -27,7 +27,5 @@ }, "markdown.copyFiles.destination": { "/docSite/content/**/*": "${documentWorkspaceFolder}/docSite/assets/imgs/" - }, - "markdown.copyFiles.overwriteBehavior": "nameIncrementally", - "markdown.copyFiles.transformPath": "const filename = uri.path.split('/').pop(); return `/imgs/${filename}`;" + } } \ No newline at end of file diff --git a/README.md b/README.md index bb73909ca9ca..2a5793baf16e 100644 --- a/README.md +++ b/README.md @@ -114,16 +114,6 @@ https://github.com/labring/FastGPT/assets/15308462/7d3a38df-eb0e-4388-9250-2409b # -## 🏘️ 社区交流群 - -扫码加入飞书话题群: - -![](https://oss.laf.run/otnvvf-imgs/fastgpt-feishu1.png) - - - # - - ## 🏘️ 加入我们 我们正在寻找志同道合的小伙伴,加速 FastGPT 的发展。你可以通过 [FastGPT 2025 招聘](https://fael3z0zfze.feishu.cn/wiki/P7FOwEmPziVcaYkvVaacnVX1nvg)了解 FastGPT 的招聘信息。 @@ -133,17 +123,26 @@ https://github.com/labring/FastGPT/assets/15308462/7d3a38df-eb0e-4388-9250-2409b - [Laf:3 分钟快速接入三方应用](https://github.com/labring/laf) - [Sealos:快速部署集群应用](https://github.com/labring/sealos) - [One API:多模型管理,支持 Azure、文心一言等](https://github.com/songquanpeng/one-api) -- [TuShan:5 分钟搭建后台管理系统](https://github.com/msgbyte/tushan) # - ## 🌿 第三方生态 -- [COW 个人微信/企微机器人](https://doc.tryfastgpt.ai/docs/use-cases/external-integration/onwechat/) +- [AI Proxy:国内模型聚合服务](https://sealos.run/aiproxy/?k=fastgpt-github/) - [SiliconCloud (硅基流动) —— 开源模型在线体验平台](https://cloud.siliconflow.cn/i/TR9Ym0c4) +- [COW 个人微信/企微机器人](https://doc.tryfastgpt.ai/docs/use-cases/external-integration/onwechat/) + + + # + + +## 🏘️ 社区交流群 + +扫码加入飞书话题群: + +![](https://oss.laf.run/otnvvf-imgs/fastgpt-feishu1.png) # diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 000000000000..562cf092e5aa --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,26 @@ +# 安全策略 + +## 漏洞报告 + +如果您发现了 FastGPT 的安全漏洞,请按照以下步骤进行报告: + +1. **报告方式** + 发送邮件至:yujinlong@sealos.io + 请备注版本以及您的 GitHub 账号 + +3. **响应时间** + - 我们会在 48 小时内确认收到您的报告 + - 一般在 3 个工作日内给出初步评估结果 + +4. **漏洞处理流程** + - 确认漏洞:我们会验证漏洞的存在性和影响范围 + - 修复开发:针对已确认的漏洞进行修复 + - 版本发布:在下一个版本更新中发布安全补丁 + - 公开披露:在修复完成后,我们会在更新日志中公布相关信息 + +5. **注意事项** + - 在漏洞未修复前,请勿公开披露漏洞详情 + - 我们欢迎负责任的漏洞披露 + - 对于重大贡献者,我们会在项目致谢名单中提及 + +感谢您为 FastGPT 的安全性做出贡献! diff --git a/files/docker/docker-compose-milvus.yml b/deploy/docker/docker-compose-milvus.yml similarity index 75% rename from files/docker/docker-compose-milvus.yml rename to deploy/docker/docker-compose-milvus.yml index 066b03a603e5..e5a90446bd22 100644 --- a/files/docker/docker-compose-milvus.yml +++ b/deploy/docker/docker-compose-milvus.yml @@ -114,15 +114,15 @@ services: # fastgpt sandbox: container_name: sandbox - image: ghcr.io/labring/fastgpt-sandbox:v4.8.22 # git - # image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt-sandbox:v4.8.22 # 阿里云 + image: ghcr.io/labring/fastgpt-sandbox:v4.9.0 # git + # image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt-sandbox:v4.9.0 # 阿里云 networks: - fastgpt restart: always fastgpt: container_name: fastgpt - image: ghcr.io/labring/fastgpt:v4.8.22 # git - # image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt:v4.8.22 # 阿里云 + image: ghcr.io/labring/fastgpt:v4.9.0 # git + # image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt:v4.9.0 # 阿里云 ports: - 3000:3000 networks: @@ -137,10 +137,13 @@ services: - FE_DOMAIN= # root 密码,用户名为: root。如果需要修改 root 密码,直接修改这个环境变量,并重启即可。 - DEFAULT_ROOT_PSW=1234 - # AI模型的API地址哦。务必加 /v1。这里默认填写了OneApi的访问地址。 - - OPENAI_BASE_URL=http://oneapi:3000/v1 - # AI模型的API Key。(这里默认填写了OneAPI的快速默认key,测试通后,务必及时修改) - - CHAT_API_KEY=sk-fastgpt + # AI Proxy 的地址,如果配了该地址,优先使用 + - AIPROXY_API_ENDPOINT=http://aiproxy:3000 + # AI Proxy 的 Admin Token,与 AI Proxy 中的环境变量 ADMIN_KEY + - AIPROXY_API_TOKEN=aiproxy + # 模型中转地址(如果用了 AI Proxy,下面 2 个就不需要了,旧版 OneAPI 用户,使用下面的变量) + # - OPENAI_BASE_URL=http://oneapi:3000/v1 + # - CHAT_API_KEY=sk-fastgpt # 数据库最大连接数 - DB_MAX_LINK=30 # 登录凭证密钥 @@ -170,48 +173,52 @@ services: volumes: - ./config.json:/app/data/config.json - # oneapi - mysql: - image: registry.cn-hangzhou.aliyuncs.com/fastgpt/mysql:8.0.36 # 阿里云 - # image: mysql:8.0.36 - container_name: mysql - restart: always - ports: - - 3306:3306 + # AI Proxy + aiproxy: + image: 'ghcr.io/labring/sealos-aiproxy-service:latest' + container_name: aiproxy + restart: unless-stopped + depends_on: + aiproxy_pg: + condition: service_healthy networks: - fastgpt - command: --default-authentication-plugin=mysql_native_password environment: - # 默认root密码,仅首次运行有效 - MYSQL_ROOT_PASSWORD: oneapimmysql - MYSQL_DATABASE: oneapi + # 对应 fastgpt 里的AIPROXY_API_TOKEN + - ADMIN_KEY=aiproxy + # 错误日志详情保存时间(小时) + - LOG_DETAIL_STORAGE_HOURS=1 + # 数据库连接地址 + - SQL_DSN=postgres://postgres:aiproxy@aiproxy_pg:5432/aiproxy + # 最大重试次数 + - RetryTimes=3 + # 不需要计费 + - BILLING_ENABLED=false + # 不需要严格检测模型 + - DISABLE_MODEL_CONFIG=true + healthcheck: + test: ['CMD', 'curl', '-f', 'http://localhost:3000/api/status'] + interval: 5s + timeout: 5s + retries: 10 + aiproxy_pg: + image: pgvector/pgvector:0.8.0-pg15 # docker hub + # image: registry.cn-hangzhou.aliyuncs.com/fastgpt/pgvector:v0.8.0-pg15 # 阿里云 + restart: unless-stopped + container_name: aiproxy_pg volumes: - - ./mysql:/var/lib/mysql - oneapi: - container_name: oneapi - image: ghcr.io/songquanpeng/one-api:v0.6.7 - # image: registry.cn-hangzhou.aliyuncs.com/fastgpt/one-api:v0.6.6 # 阿里云 - ports: - - 3001:3000 - depends_on: - - mysql + - ./aiproxy_pg:/var/lib/postgresql/data networks: - fastgpt - restart: always environment: - # mysql 连接参数 - - SQL_DSN=root:oneapimmysql@tcp(mysql:3306)/oneapi - # 登录凭证加密密钥 - - SESSION_SECRET=oneapikey - # 内存缓存 - - MEMORY_CACHE_ENABLED=true - # 启动聚合更新,减少数据交互频率 - - BATCH_UPDATE_ENABLED=true - # 聚合更新时长 - - BATCH_UPDATE_INTERVAL=10 - # 初始化的 root 密钥(建议部署完后更改,否则容易泄露) - - INITIAL_ROOT_TOKEN=fastgpt - volumes: - - ./oneapi:/data + TZ: Asia/Shanghai + POSTGRES_USER: postgres + POSTGRES_DB: aiproxy + POSTGRES_PASSWORD: aiproxy + healthcheck: + test: ['CMD', 'pg_isready', '-U', 'postgres', '-d', 'aiproxy'] + interval: 5s + timeout: 5s + retries: 10 networks: fastgpt: diff --git a/files/docker/docker-compose-pgvector.yml b/deploy/docker/docker-compose-pgvector.yml similarity index 68% rename from files/docker/docker-compose-pgvector.yml rename to deploy/docker/docker-compose-pgvector.yml index 3280b4019852..0d763c912cd5 100644 --- a/files/docker/docker-compose-pgvector.yml +++ b/deploy/docker/docker-compose-pgvector.yml @@ -7,12 +7,12 @@ version: '3.3' services: # db pg: - image: pgvector/pgvector:0.7.0-pg15 # docker hub - # image: registry.cn-hangzhou.aliyuncs.com/fastgpt/pgvector:v0.7.0 # 阿里云 + image: pgvector/pgvector:0.8.0-pg15 # docker hub + # image: registry.cn-hangzhou.aliyuncs.com/fastgpt/pgvector:v0.8.0-pg15 # 阿里云 container_name: pg restart: always - ports: # 生产环境建议不要暴露 - - 5432:5432 + # ports: # 生产环境建议不要暴露 + # - 5432:5432 networks: - fastgpt environment: @@ -28,8 +28,8 @@ services: # image: mongo:4.4.29 # cpu不支持AVX时候使用 container_name: mongo restart: always - ports: - - 27017:27017 + # ports: + # - 27017:27017 networks: - fastgpt command: mongod --keyFile /data/mongodb.key --replSet rs0 @@ -72,15 +72,15 @@ services: # fastgpt sandbox: container_name: sandbox - image: ghcr.io/labring/fastgpt-sandbox:v4.8.22 # git - # image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt-sandbox:v4.8.22 # 阿里云 + image: ghcr.io/labring/fastgpt-sandbox:v4.9.0 # git + # image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt-sandbox:v4.9.0 # 阿里云 networks: - fastgpt restart: always fastgpt: container_name: fastgpt - image: ghcr.io/labring/fastgpt:v4.8.22 # git - # image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt:v4.8.22 # 阿里云 + image: ghcr.io/labring/fastgpt:v4.9.0 # git + # image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt:v4.9.0 # 阿里云 ports: - 3000:3000 networks: @@ -95,10 +95,13 @@ services: - FE_DOMAIN= # root 密码,用户名为: root。如果需要修改 root 密码,直接修改这个环境变量,并重启即可。 - DEFAULT_ROOT_PSW=1234 - # AI模型的API地址哦。务必加 /v1。这里默认填写了OneApi的访问地址。 - - OPENAI_BASE_URL=http://oneapi:3000/v1 - # AI模型的API Key。(这里默认填写了OneAPI的快速默认key,测试通后,务必及时修改) - - CHAT_API_KEY=sk-fastgpt + # AI Proxy 的地址,如果配了该地址,优先使用 + - AIPROXY_API_ENDPOINT=http://aiproxy:3000 + # AI Proxy 的 Admin Token,与 AI Proxy 中的环境变量 ADMIN_KEY + - AIPROXY_API_TOKEN=aiproxy + # 模型中转地址(如果用了 AI Proxy,下面 2 个就不需要了,旧版 OneAPI 用户,使用下面的变量) + # - OPENAI_BASE_URL=http://oneapi:3000/v1 + # - CHAT_API_KEY=sk-fastgpt # 数据库最大连接数 - DB_MAX_LINK=30 # 登录凭证密钥 @@ -127,48 +130,52 @@ services: volumes: - ./config.json:/app/data/config.json - # oneapi - mysql: - # image: registry.cn-hangzhou.aliyuncs.com/fastgpt/mysql:8.0.36 # 阿里云 - image: mysql:8.0.36 - container_name: mysql - restart: always - ports: - - 3306:3306 + # AI Proxy + aiproxy: + image: 'ghcr.io/labring/sealos-aiproxy-service:latest' + container_name: aiproxy + restart: unless-stopped + depends_on: + aiproxy_pg: + condition: service_healthy networks: - fastgpt - command: --default-authentication-plugin=mysql_native_password environment: - # 默认root密码,仅首次运行有效 - MYSQL_ROOT_PASSWORD: oneapimmysql - MYSQL_DATABASE: oneapi + # 对应 fastgpt 里的AIPROXY_API_TOKEN + - ADMIN_KEY=aiproxy + # 错误日志详情保存时间(小时) + - LOG_DETAIL_STORAGE_HOURS=1 + # 数据库连接地址 + - SQL_DSN=postgres://postgres:aiproxy@aiproxy_pg:5432/aiproxy + # 最大重试次数 + - RetryTimes=3 + # 不需要计费 + - BILLING_ENABLED=false + # 不需要严格检测模型 + - DISABLE_MODEL_CONFIG=true + healthcheck: + test: ['CMD', 'curl', '-f', 'http://localhost:3000/api/status'] + interval: 5s + timeout: 5s + retries: 10 + aiproxy_pg: + image: pgvector/pgvector:0.8.0-pg15 # docker hub + # image: registry.cn-hangzhou.aliyuncs.com/fastgpt/pgvector:v0.8.0-pg15 # 阿里云 + restart: unless-stopped + container_name: aiproxy_pg volumes: - - ./mysql:/var/lib/mysql - oneapi: - container_name: oneapi - image: ghcr.io/songquanpeng/one-api:v0.6.7 - # image: registry.cn-hangzhou.aliyuncs.com/fastgpt/one-api:v0.6.6 # 阿里云 - ports: - - 3001:3000 - depends_on: - - mysql + - ./aiproxy_pg:/var/lib/postgresql/data networks: - fastgpt - restart: always environment: - # mysql 连接参数 - - SQL_DSN=root:oneapimmysql@tcp(mysql:3306)/oneapi - # 登录凭证加密密钥 - - SESSION_SECRET=oneapikey - # 内存缓存 - - MEMORY_CACHE_ENABLED=true - # 启动聚合更新,减少数据交互频率 - - BATCH_UPDATE_ENABLED=true - # 聚合更新时长 - - BATCH_UPDATE_INTERVAL=10 - # 初始化的 root 密钥(建议部署完后更改,否则容易泄露) - - INITIAL_ROOT_TOKEN=fastgpt - volumes: - - ./oneapi:/data + TZ: Asia/Shanghai + POSTGRES_USER: postgres + POSTGRES_DB: aiproxy + POSTGRES_PASSWORD: aiproxy + healthcheck: + test: ['CMD', 'pg_isready', '-U', 'postgres', '-d', 'aiproxy'] + interval: 5s + timeout: 5s + retries: 10 networks: fastgpt: diff --git a/files/docker/docker-compose-zilliz.yml b/deploy/docker/docker-compose-zilliz.yml similarity index 68% rename from files/docker/docker-compose-zilliz.yml rename to deploy/docker/docker-compose-zilliz.yml index 5f313cee182f..c4cafe71cf50 100644 --- a/files/docker/docker-compose-zilliz.yml +++ b/deploy/docker/docker-compose-zilliz.yml @@ -53,15 +53,15 @@ services: wait $$! sandbox: container_name: sandbox - image: ghcr.io/labring/fastgpt-sandbox:v4.8.22 # git - # image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt-sandbox:v4.8.22 # 阿里云 + image: ghcr.io/labring/fastgpt-sandbox:v4.9.0 # git + # image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt-sandbox:v4.9.0 # 阿里云 networks: - fastgpt restart: always fastgpt: container_name: fastgpt - image: ghcr.io/labring/fastgpt:v4.8.22 # git - # image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt:v4.8.22 # 阿里云 + image: ghcr.io/labring/fastgpt:v4.9.0 # git + # image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt:v4.9.0 # 阿里云 ports: - 3000:3000 networks: @@ -75,10 +75,13 @@ services: - FE_DOMAIN= # root 密码,用户名为: root。如果需要修改 root 密码,直接修改这个环境变量,并重启即可。 - DEFAULT_ROOT_PSW=1234 - # AI模型的API地址哦。务必加 /v1。这里默认填写了OneApi的访问地址。 - - OPENAI_BASE_URL=http://oneapi:3000/v1 - # AI模型的API Key。(这里默认填写了OneAPI的快速默认key,测试通后,务必及时修改) - - CHAT_API_KEY=sk-fastgpt + # AI Proxy 的地址,如果配了该地址,优先使用 + - AIPROXY_API_ENDPOINT=http://aiproxy:3000 + # AI Proxy 的 Admin Token,与 AI Proxy 中的环境变量 ADMIN_KEY + - AIPROXY_API_TOKEN=aiproxy + # 模型中转地址(如果用了 AI Proxy,下面 2 个就不需要了,旧版 OneAPI 用户,使用下面的变量) + # - OPENAI_BASE_URL=http://oneapi:3000/v1 + # - CHAT_API_KEY=sk-fastgpt # 数据库最大连接数 - DB_MAX_LINK=30 # 登录凭证密钥 @@ -108,48 +111,52 @@ services: volumes: - ./config.json:/app/data/config.json - # oneapi - mysql: - image: registry.cn-hangzhou.aliyuncs.com/fastgpt/mysql:8.0.36 # 阿里云 - # image: mysql:8.0.36 - container_name: mysql - restart: always - ports: - - 3306:3306 + # AI Proxy + aiproxy: + image: 'ghcr.io/labring/sealos-aiproxy-service:latest' + container_name: aiproxy + restart: unless-stopped + depends_on: + aiproxy_pg: + condition: service_healthy networks: - fastgpt - command: --default-authentication-plugin=mysql_native_password environment: - # 默认root密码,仅首次运行有效 - MYSQL_ROOT_PASSWORD: oneapimmysql - MYSQL_DATABASE: oneapi + # 对应 fastgpt 里的AIPROXY_API_TOKEN + - ADMIN_KEY=aiproxy + # 错误日志详情保存时间(小时) + - LOG_DETAIL_STORAGE_HOURS=1 + # 数据库连接地址 + - SQL_DSN=postgres://postgres:aiproxy@aiproxy_pg:5432/aiproxy + # 最大重试次数 + - RetryTimes=3 + # 不需要计费 + - BILLING_ENABLED=false + # 不需要严格检测模型 + - DISABLE_MODEL_CONFIG=true + healthcheck: + test: ['CMD', 'curl', '-f', 'http://localhost:3000/api/status'] + interval: 5s + timeout: 5s + retries: 10 + aiproxy_pg: + image: pgvector/pgvector:0.8.0-pg15 # docker hub + # image: registry.cn-hangzhou.aliyuncs.com/fastgpt/pgvector:v0.8.0-pg15 # 阿里云 + restart: unless-stopped + container_name: aiproxy_pg volumes: - - ./mysql:/var/lib/mysql - oneapi: - container_name: oneapi - image: ghcr.io/songquanpeng/one-api:v0.6.7 - # image: registry.cn-hangzhou.aliyuncs.com/fastgpt/one-api:v0.6.6 # 阿里云 - ports: - - 3001:3000 - depends_on: - - mysql + - ./aiproxy_pg:/var/lib/postgresql/data networks: - fastgpt - restart: always environment: - # mysql 连接参数 - - SQL_DSN=root:oneapimmysql@tcp(mysql:3306)/oneapi - # 登录凭证加密密钥 - - SESSION_SECRET=oneapikey - # 内存缓存 - - MEMORY_CACHE_ENABLED=true - # 启动聚合更新,减少数据交互频率 - - BATCH_UPDATE_ENABLED=true - # 聚合更新时长 - - BATCH_UPDATE_INTERVAL=10 - # 初始化的 root 密钥(建议部署完后更改,否则容易泄露) - - INITIAL_ROOT_TOKEN=fastgpt - volumes: - - ./oneapi:/data + TZ: Asia/Shanghai + POSTGRES_USER: postgres + POSTGRES_DB: aiproxy + POSTGRES_PASSWORD: aiproxy + healthcheck: + test: ['CMD', 'pg_isready', '-U', 'postgres', '-d', 'aiproxy'] + interval: 5s + timeout: 5s + retries: 10 networks: fastgpt: diff --git a/files/docker/docker-compose/docker-compose b/deploy/docker/docker-compose/docker-compose similarity index 100% rename from files/docker/docker-compose/docker-compose rename to deploy/docker/docker-compose/docker-compose diff --git a/files/docker/docker-compose/init.sh b/deploy/docker/docker-compose/init.sh similarity index 100% rename from files/docker/docker-compose/init.sh rename to deploy/docker/docker-compose/init.sh diff --git a/files/docker/run.sh b/deploy/docker/run.sh similarity index 100% rename from files/docker/run.sh rename to deploy/docker/run.sh diff --git a/files/helm/fastgpt/.helmignore b/deploy/helm/fastgpt/.helmignore similarity index 100% rename from files/helm/fastgpt/.helmignore rename to deploy/helm/fastgpt/.helmignore diff --git a/files/helm/fastgpt/Chart.lock b/deploy/helm/fastgpt/Chart.lock similarity index 100% rename from files/helm/fastgpt/Chart.lock rename to deploy/helm/fastgpt/Chart.lock diff --git a/files/helm/fastgpt/Chart.yaml b/deploy/helm/fastgpt/Chart.yaml similarity index 100% rename from files/helm/fastgpt/Chart.yaml rename to deploy/helm/fastgpt/Chart.yaml diff --git a/files/helm/fastgpt/README.md b/deploy/helm/fastgpt/README.md similarity index 100% rename from files/helm/fastgpt/README.md rename to deploy/helm/fastgpt/README.md diff --git a/files/helm/fastgpt/templates/NOTES.txt b/deploy/helm/fastgpt/templates/NOTES.txt similarity index 100% rename from files/helm/fastgpt/templates/NOTES.txt rename to deploy/helm/fastgpt/templates/NOTES.txt diff --git a/files/helm/fastgpt/templates/_helpers.tpl b/deploy/helm/fastgpt/templates/_helpers.tpl similarity index 100% rename from files/helm/fastgpt/templates/_helpers.tpl rename to deploy/helm/fastgpt/templates/_helpers.tpl diff --git a/files/helm/fastgpt/templates/configmap-config.yaml b/deploy/helm/fastgpt/templates/configmap-config.yaml similarity index 99% rename from files/helm/fastgpt/templates/configmap-config.yaml rename to deploy/helm/fastgpt/templates/configmap-config.yaml index 0aa7dcc900a6..4a760d560884 100644 --- a/files/helm/fastgpt/templates/configmap-config.yaml +++ b/deploy/helm/fastgpt/templates/configmap-config.yaml @@ -6,6 +6,7 @@ data: "openapiPrefix": "fastgpt", "vectorMaxProcess": 15, "qaMaxProcess": 15, + "vlmMaxProcess": 15, "pgHNSWEfSearch": 100 }, "llmModels": [ diff --git a/files/helm/fastgpt/templates/deployment.yaml b/deploy/helm/fastgpt/templates/deployment.yaml similarity index 100% rename from files/helm/fastgpt/templates/deployment.yaml rename to deploy/helm/fastgpt/templates/deployment.yaml diff --git a/files/helm/fastgpt/templates/hpa.yaml b/deploy/helm/fastgpt/templates/hpa.yaml similarity index 100% rename from files/helm/fastgpt/templates/hpa.yaml rename to deploy/helm/fastgpt/templates/hpa.yaml diff --git a/files/helm/fastgpt/templates/ingress.yaml b/deploy/helm/fastgpt/templates/ingress.yaml similarity index 100% rename from files/helm/fastgpt/templates/ingress.yaml rename to deploy/helm/fastgpt/templates/ingress.yaml diff --git a/files/helm/fastgpt/templates/secret-env.yaml b/deploy/helm/fastgpt/templates/secret-env.yaml similarity index 100% rename from files/helm/fastgpt/templates/secret-env.yaml rename to deploy/helm/fastgpt/templates/secret-env.yaml diff --git a/files/helm/fastgpt/templates/service.yaml b/deploy/helm/fastgpt/templates/service.yaml similarity index 100% rename from files/helm/fastgpt/templates/service.yaml rename to deploy/helm/fastgpt/templates/service.yaml diff --git a/files/helm/fastgpt/templates/serviceaccount.yaml b/deploy/helm/fastgpt/templates/serviceaccount.yaml similarity index 100% rename from files/helm/fastgpt/templates/serviceaccount.yaml rename to deploy/helm/fastgpt/templates/serviceaccount.yaml diff --git a/files/helm/fastgpt/templates/tests/test-connection.yaml b/deploy/helm/fastgpt/templates/tests/test-connection.yaml similarity index 100% rename from files/helm/fastgpt/templates/tests/test-connection.yaml rename to deploy/helm/fastgpt/templates/tests/test-connection.yaml diff --git a/files/helm/fastgpt/values.yaml b/deploy/helm/fastgpt/values.yaml similarity index 100% rename from files/helm/fastgpt/values.yaml rename to deploy/helm/fastgpt/values.yaml diff --git a/docSite/assets/imgs/aiproxy-1.jpg b/docSite/assets/imgs/aiproxy-1.jpg new file mode 100644 index 000000000000..1b8e8eaf7f9b Binary files /dev/null and b/docSite/assets/imgs/aiproxy-1.jpg differ diff --git a/docSite/assets/imgs/aiproxy-1.png b/docSite/assets/imgs/aiproxy-1.png new file mode 100644 index 000000000000..1b8e8eaf7f9b Binary files /dev/null and b/docSite/assets/imgs/aiproxy-1.png differ diff --git a/docSite/assets/imgs/aiproxy-10.png b/docSite/assets/imgs/aiproxy-10.png new file mode 100644 index 000000000000..e635a0fe36a2 Binary files /dev/null and b/docSite/assets/imgs/aiproxy-10.png differ diff --git a/docSite/assets/imgs/aiproxy-11.png b/docSite/assets/imgs/aiproxy-11.png new file mode 100644 index 000000000000..0a87605aef5a Binary files /dev/null and b/docSite/assets/imgs/aiproxy-11.png differ diff --git a/docSite/assets/imgs/aiproxy-2.png b/docSite/assets/imgs/aiproxy-2.png new file mode 100644 index 000000000000..2f8e8a03706d Binary files /dev/null and b/docSite/assets/imgs/aiproxy-2.png differ diff --git a/docSite/assets/imgs/aiproxy-3.png b/docSite/assets/imgs/aiproxy-3.png new file mode 100644 index 000000000000..769d22496175 Binary files /dev/null and b/docSite/assets/imgs/aiproxy-3.png differ diff --git a/docSite/assets/imgs/aiproxy-4.png b/docSite/assets/imgs/aiproxy-4.png new file mode 100644 index 000000000000..b2e815b84849 Binary files /dev/null and b/docSite/assets/imgs/aiproxy-4.png differ diff --git a/docSite/assets/imgs/aiproxy-5.png b/docSite/assets/imgs/aiproxy-5.png new file mode 100644 index 000000000000..11d5546c0839 Binary files /dev/null and b/docSite/assets/imgs/aiproxy-5.png differ diff --git a/docSite/assets/imgs/aiproxy-6.png b/docSite/assets/imgs/aiproxy-6.png new file mode 100644 index 000000000000..6ea3ff906073 Binary files /dev/null and b/docSite/assets/imgs/aiproxy-6.png differ diff --git a/docSite/assets/imgs/aiproxy-7.png b/docSite/assets/imgs/aiproxy-7.png new file mode 100644 index 000000000000..0c23fb606a6a Binary files /dev/null and b/docSite/assets/imgs/aiproxy-7.png differ diff --git a/docSite/assets/imgs/aiproxy-8.png b/docSite/assets/imgs/aiproxy-8.png new file mode 100644 index 000000000000..14e392669a1a Binary files /dev/null and b/docSite/assets/imgs/aiproxy-8.png differ diff --git a/docSite/assets/imgs/aiproxy-9.png b/docSite/assets/imgs/aiproxy-9.png new file mode 100644 index 000000000000..0851486fb388 Binary files /dev/null and b/docSite/assets/imgs/aiproxy-9.png differ diff --git a/docSite/assets/imgs/aiproxy1.png b/docSite/assets/imgs/aiproxy1.png new file mode 100644 index 000000000000..3905df329d60 Binary files /dev/null and b/docSite/assets/imgs/aiproxy1.png differ diff --git a/docSite/assets/imgs/image copy.png b/docSite/assets/imgs/image copy.png new file mode 100644 index 000000000000..21184181b101 Binary files /dev/null and b/docSite/assets/imgs/image copy.png differ diff --git a/docSite/assets/imgs/marker2.png b/docSite/assets/imgs/marker2.png new file mode 100644 index 000000000000..20d6e61038c4 Binary files /dev/null and b/docSite/assets/imgs/marker2.png differ diff --git a/docSite/assets/imgs/marker3.png b/docSite/assets/imgs/marker3.png new file mode 100644 index 000000000000..31d0586cf151 Binary files /dev/null and b/docSite/assets/imgs/marker3.png differ diff --git a/docSite/content/zh-cn/docs/development/configuration.md b/docSite/content/zh-cn/docs/development/configuration.md index 90434d66377d..8e5affee42c7 100644 --- a/docSite/content/zh-cn/docs/development/configuration.md +++ b/docSite/content/zh-cn/docs/development/configuration.md @@ -23,8 +23,54 @@ weight: 707 "systemEnv": { "vectorMaxProcess": 15, // 向量处理线程数量 "qaMaxProcess": 15, // 问答拆分线程数量 + "vlmMaxProcess": 15, // 图片理解模型最大处理进程 "tokenWorkers": 50, // Token 计算线程保持数,会持续占用内存,不能设置太大。 - "pgHNSWEfSearch": 100 // 向量搜索参数。越大,搜索越精确,但是速度越慢。设置为100,有99%+精度。 + "pgHNSWEfSearch": 100, // 向量搜索参数。越大,搜索越精确,但是速度越慢。设置为100,有99%+精度。 + "customPdfParse": { // 4.9.0 新增配置 + "url": "", // 自定义 PDF 解析服务地址 + "key": "", // 自定义 PDF 解析服务密钥 + "doc2xKey": "", // doc2x 服务密钥 + "price": 0 // PDF 解析服务价格 + } } } ``` + +## 自定义 PDF 解析配置 + +自定义 PDF 服务解析的优先级高于 Doc2x 服务,所以如果使用 Doc2x 服务,请勿配置自定义 PDF 服务。 + +### 使用 Sealos PDF 解析服务 + +#### 1. 申请 Sealos AI proxy API Key + +[点击打开 Sealos Pdf parser 官网](https://cloud.sealos.run/?uid=fnWRt09fZP&openapp=system-aiproxy),并进行对应 API Key 的申请。 + +#### 2. 修改 FastGPT 配置文件 + +`systemEnv.customPdfParse.url`填写成`https://aiproxy.hzh.sealos.run/v1/parse/pdf?model=parse-pdf` +`systemEnv.customPdfParse.key`填写成在 Sealos AI proxy 中申请的 API Key。 + +![](/imgs/deployconfig-aiproxy.png) + +### 使用 Doc2x 解析 PDF 文件 + +`Doc2x`是一个国内提供专业 PDF 解析。 + +#### 1. 申请 Doc2x 服务 + +[点击打开 Doc2x 官网](https://doc2x.noedgeai.com?inviteCode=9EACN2),并进行对应 API Key 的申请。 + +#### 2. 修改 FastGPT 配置文件 + +开源版用户在 `config.json` 文件中添加 `systemEnv.customPdfParse.doc2xKey` 配置,并填写上申请到的 API Key。并重启服务。 + +商业版用户在 Admin 后台根据表单指引填写 Doc2x 服务密钥。 + +#### 3. 开始使用 + +在知识库导入数据或应用文件上传配置中,可以勾选`PDF 增强解析`,则在对 PDF 解析时候,会使用 Doc2x 服务进行解析。 + +### 使用 Marker 解析 PDF 文件 + +[点击查看 Marker 接入教程](/docs/development/custom-models/marker) \ No newline at end of file diff --git a/docSite/content/zh-cn/docs/development/custom-models/bge-rerank.md b/docSite/content/zh-cn/docs/development/custom-models/bge-rerank.md index ef1f27ac795c..1b9e3d133471 100644 --- a/docSite/content/zh-cn/docs/development/custom-models/bge-rerank.md +++ b/docSite/content/zh-cn/docs/development/custom-models/bge-rerank.md @@ -31,9 +31,9 @@ weight: 920 3 个模型代码分别为: -1. [https://github.com/labring/FastGPT/tree/main/python/bge-rerank/bge-reranker-base](https://github.com/labring/FastGPT/tree/main/python/bge-rerank/bge-reranker-base) -2. [https://github.com/labring/FastGPT/tree/main/python/bge-rerank/bge-reranker-large](https://github.com/labring/FastGPT/tree/main/python/bge-rerank/bge-reranker-large) -3. [https://github.com/labring/FastGPT/tree/main/python/bge-rerank/bge-reranker-v2-m3](https://github.com/labring/FastGPT/tree/main/python/bge-rerank/bge-reranker-v2-m3) +1. [https://github.com/labring/FastGPT/tree/main/plugins/rerank-bge/bge-reranker-base](https://github.com/labring/FastGPT/tree/main/plugins/rerank-bge/bge-reranker-base) +2. [https://github.com/labring/FastGPT/tree/main/plugins/rerank-bge/bge-reranker-large](https://github.com/labring/FastGPT/tree/main/plugins/rerank-bge/bge-reranker-large) +3. [https://github.com/labring/FastGPT/tree/main/plugins/rerank-bge/bge-reranker-v2-m3](https://github.com/labring/FastGPT/tree/main/plugins/rerank-bge/bge-reranker-v2-m3) ### 3. 安装依赖 diff --git a/docSite/content/zh-cn/docs/development/custom-models/marker.md b/docSite/content/zh-cn/docs/development/custom-models/marker.md index ce68431a8561..36bbdfe684fc 100644 --- a/docSite/content/zh-cn/docs/development/custom-models/marker.md +++ b/docSite/content/zh-cn/docs/development/custom-models/marker.md @@ -11,39 +11,51 @@ weight: 909 PDF 是一个相对复杂的文件格式,在 FastGPT 内置的 pdf 解析器中,依赖的是 pdfjs 库解析,该库基于逻辑解析,无法有效的理解复杂的 pdf 文件。所以我们在解析 pdf 时候,如果遇到图片、表格、公式等非简单文本内容,会发现解析效果不佳。 -市面上目前有多种解析 PDF 的方法,比如使用 [Marker](https://github.com/VikParuchuri/marker),该项目使用了 Surya 模型,基于视觉解析,可以有效提取图片、表格、公式等复杂内容。为了可以让 Marker 快速接入 FastGPT,我们做了一个自定义解析的拓展 Demo。 +市面上目前有多种解析 PDF 的方法,比如使用 [Marker](https://github.com/VikParuchuri/marker),该项目使用了 Surya 模型,基于视觉解析,可以有效提取图片、表格、公式等复杂内容。 -在 FastGPT 4.8.15 版本中,你可以通过增加一个环境变量,来替换掉 FastGPT 系统内置解析器,实现自定义的文档解析服务。该功能只是 Demo 阶段,后期配置模式和交互规则会发生改动。 +在 `FastGPT v4.9.0` 版本中,开源版用户可以在`config.json`文件中添加`systemEnv.customPdfParse`配置,来使用 Marker 解析 PDF 文件。商业版用户直接在 Admin 后台根据表单指引填写即可。需重新拉取 Marker 镜像,接口格式已变动。 ## 使用教程 -### 1. 按照 Marker +### 1. 安装 Marker -参考文档 [Marker 安装教程](https://github.com/labring/FastGPT/tree/main/python/pdf-marker),安装 Marker 模型。封装的 API 已经适配了 FastGPT 自定义解析服务。 +参考文档 [Marker 安装教程](https://github.com/labring/FastGPT/tree/main/plugins/model/pdf-marker),安装 Marker 模型。封装的 API 已经适配了 FastGPT 自定义解析服务。 这里介绍快速 Docker 安装的方法: ```dockerfile -docker pull crpi-h3snc261q1dosroc.cn-hangzhou.personal.cr.aliyuncs.com/marker11/marker_images:latest -docker run --gpus all -itd -p 7231:7231 --name model_pdf_v1 crpi-h3snc261q1dosroc.cn-hangzhou.personal.cr.aliyuncs.com/marker11/marker_images:latest +docker pull crpi-h3snc261q1dosroc.cn-hangzhou.personal.cr.aliyuncs.com/marker11/marker_images:v0.2 +docker run --gpus all -itd -p 7231:7232 --name model_pdf_v2 -e PROCESSES_PER_GPU="2" crpi-h3snc261q1dosroc.cn-hangzhou.personal.cr.aliyuncs.com/marker11/marker_images:v0.2 ``` - -### 2. 添加 FastGPT 环境变量 - -``` -CUSTOM_READ_FILE_URL=http://xxxx.com/v1/parse/file -CUSTOM_READ_FILE_EXTENSION=pdf +### 2. 添加 FastGPT 文件配置 + +```json +{ + xxx + "systemEnv": { + xxx + "customPdfParse": { + "url": "http://xxxx.com/v2/parse/file", // 自定义 PDF 解析服务地址 marker v0.2 + "key": "", // 自定义 PDF 解析服务密钥 + "doc2xKey": "", // doc2x 服务密钥 + "price": 0 // PDF 解析服务价格 + } + } +} ``` -* CUSTOM_READ_FILE_URL - 自定义解析服务的地址, host改成解析服务的访问地址,path 不能变动。 -* CUSTOM_READ_FILE_EXTENSION - 支持的文件后缀,多个文件类型,可用逗号隔开。 +需要重启服务。 ### 3. 测试效果 -通过知识库上传一个 pdf 文件,并确认上传,可以在日志中看到 LOG (LOG_LEVEL需要设置 info 或者 debug): +通过知识库上传一个 pdf 文件,并勾选上 `PDF 增强解析`。 + +![alt text](/imgs/marker2.png) + +确认上传后,可以在日志中看到 LOG (LOG_LEVEL需要设置 info 或者 debug): ``` -[Info] 2024-12-05 15:04:42 Parsing files from an external service +[Info] 2024-12-05 15:04:42 Parsing files from an external service [Info] 2024-12-05 15:07:08 Custom file parsing is complete, time: 1316ms ``` @@ -51,6 +63,10 @@ CUSTOM_READ_FILE_EXTENSION=pdf ![alt text](/imgs/image-10.png) +同样的,在应用中,你可以在文件上传配置里,勾选上 `PDF 增强解析`。 + +![alt text](/imgs/marker3.png) + ## 效果展示 @@ -63,4 +79,25 @@ CUSTOM_READ_FILE_EXTENSION=pdf 上图是分块后的结果,下图是 pdf 原文。整体图片、公式、表格都可以提取出来,效果还是杠杠的。 -不过要注意的是,[Marker](https://github.com/VikParuchuri/marker) 的协议是`GPL-3.0 license`,请在遵守协议的前提下使用。 \ No newline at end of file +不过要注意的是,[Marker](https://github.com/VikParuchuri/marker) 的协议是`GPL-3.0 license`,请在遵守协议的前提下使用。 + +## 旧版 Marker 使用方法 + +FastGPT V4.9.0 版本之前,可以用以下方式,试用 Marker 解析服务。 + +安装和运行 Marker 服务: + +```dockerfile +docker pull crpi-h3snc261q1dosroc.cn-hangzhou.personal.cr.aliyuncs.com/marker11/marker_images:v0.1 +docker run --gpus all -itd -p 7231:7231 --name model_pdf_v1 -e PROCESSES_PER_GPU="2" crpi-h3snc261q1dosroc.cn-hangzhou.personal.cr.aliyuncs.com/marker11/marker_images:v0.1 +``` + +并修改 FastGPT 环境变量: + +``` +CUSTOM_READ_FILE_URL=http://xxxx.com/v1/parse/file +CUSTOM_READ_FILE_EXTENSION=pdf +``` + +* CUSTOM_READ_FILE_URL - 自定义解析服务的地址, host改成解析服务的访问地址,path 不能变动。 +* CUSTOM_READ_FILE_EXTENSION - 支持的文件后缀,多个文件类型,可用逗号隔开。 \ No newline at end of file diff --git a/docSite/content/zh-cn/docs/development/docker.md b/docSite/content/zh-cn/docs/development/docker.md index 22be77625d73..9a5962a046d0 100644 --- a/docSite/content/zh-cn/docs/development/docker.md +++ b/docSite/content/zh-cn/docs/development/docker.md @@ -30,7 +30,7 @@ weight: 707 ### PgVector版本 -非常轻量,适合数据量在 5000 万以下。 +非常轻量,适合知识库索引量在 5000 万以下。 {{< table "table-hover table-striped-columns" >}} | 环境 | 最低配置(单节点) | 推荐配置 | @@ -118,7 +118,7 @@ brew install orbstack 非 Linux 环境或无法访问外网环境,可手动创建一个目录,并下载配置文件和对应版本的`docker-compose.yml`,在这个文件夹中依据下载的配置文件运行docker,若作为本地开发使用推荐`docker-compose-pgvector`版本,并且自行拉取并运行`sandbox`和`fastgpt`,并在docker配置文件中注释掉`sandbox`和`fastgpt`的部分 - [config.json](https://raw.githubusercontent.com/labring/FastGPT/refs/heads/main/projects/app/data/config.json) -- [docker-compose.yml](https://github.com/labring/FastGPT/blob/main/files/docker) (注意,不同向量库版本的文件不一样) +- [docker-compose.yml](https://github.com/labring/FastGPT/blob/main/deploy/docker) (注意,不同向量库版本的文件不一样) {{% alert icon="🤖" context="success" %}} @@ -134,11 +134,11 @@ cd fastgpt curl -O https://raw.githubusercontent.com/labring/FastGPT/main/projects/app/data/config.json # pgvector 版本(测试推荐,简单快捷) -curl -o docker-compose.yml https://raw.githubusercontent.com/labring/FastGPT/main/files/docker/docker-compose-pgvector.yml +curl -o docker-compose.yml https://raw.githubusercontent.com/labring/FastGPT/main/deploy/docker/docker-compose-pgvector.yml # milvus 版本 -# curl -o docker-compose.yml https://raw.githubusercontent.com/labring/FastGPT/main/files/docker/docker-compose-milvus.yml +# curl -o docker-compose.yml https://raw.githubusercontent.com/labring/FastGPT/main/deploy/docker/docker-compose-milvus.yml # zilliz 版本 -# curl -o docker-compose.yml https://raw.githubusercontent.com/labring/FastGPT/main/files/docker/docker-compose-zilliz.yml +# curl -o docker-compose.yml https://raw.githubusercontent.com/labring/FastGPT/main/deploy/docker/docker-compose-zilliz.yml ``` ### 2. 修改环境变量 @@ -149,18 +149,14 @@ curl -o docker-compose.yml https://raw.githubusercontent.com/labring/FastGPT/mai {{< tab tabName="PgVector版本" >}} {{< markdownify >}} -``` -FE_DOMAIN=你的前端你访问地址,例如 http://192.168.0.1:3000;https://cloud.fastgpt.cn -``` +无需操作 {{< /markdownify >}} {{< /tab >}} {{< tab tabName="Milvus版本" >}} {{< markdownify >}} -``` -FE_DOMAIN=你的前端你访问地址,例如 http://192.168.0.1:3000;https://cloud.fastgpt.cn -``` +无需操作 {{< /markdownify >}} {{< /tab >}} @@ -174,7 +170,6 @@ FE_DOMAIN=你的前端你访问地址,例如 http://192.168.0.1:3000;https://clo {{% alert icon="🤖" context="success" %}} 1. 修改`MILVUS_ADDRESS`和`MILVUS_TOKEN`链接参数,分别对应 `zilliz` 的 `Public Endpoint` 和 `Api key`,记得把自己ip加入白名单。 -2. 修改FE_DOMAIN=你的前端你访问地址,例如 http://192.168.0.1:3000;https://cloud.fastgpt.cn {{% /alert %}} @@ -189,34 +184,28 @@ FE_DOMAIN=你的前端你访问地址,例如 http://192.168.0.1:3000;https://clo ```bash # 启动容器 docker-compose up -d -# 等待10s,OneAPI第一次总是要重启几次才能连上Mysql -sleep 10 -# 重启一次oneapi(由于OneAPI的默认Key有点问题,不重启的话会提示找不到渠道,临时手动重启一次解决,等待作者修复) -docker restart oneapi ``` -### 4. 打开 OneAPI 添加模型 - -可以通过`ip:3001`访问OneAPI,默认账号为`root`密码为`123456`。 +### 4. 访问 FastGPT -在OneApi中添加合适的AI模型渠道。[点击查看相关教程](/docs/development/modelconfig/one-api/) - -### 5. 访问 FastGPT - -目前可以通过 `ip:3000` 直接访问(注意防火墙)。登录用户名为 `root`,密码为`docker-compose.yml`环境变量里设置的 `DEFAULT_ROOT_PSW`。 +目前可以通过 `ip:3000` 直接访问(注意开放防火墙)。登录用户名为 `root`,密码为`docker-compose.yml`环境变量里设置的 `DEFAULT_ROOT_PSW`。 如果需要域名访问,请自行安装并配置 Nginx。 -首次运行,会自动初始化 root 用户,密码为 `1234`(与环境变量中的`DEFAULT_ROOT_PSW`一致),日志里会提示一次`MongoServerError: Unable to read from a snapshot due to pending collection catalog changes;`可忽略。 - -### 6. 配置模型 +首次运行,会自动初始化 root 用户,密码为 `1234`(与环境变量中的`DEFAULT_ROOT_PSW`一致),日志可能会提示一次`MongoServerError: Unable to read from a snapshot due to pending collection catalog changes;`可忽略。 -务必先配置至少一组模型,否则系统无法正常使用。 +### 5. 配置模型 -[点击查看模型配置教程](/docs/development/modelConfig/intro/) +- 首次登录FastGPT后,系统会提示未配置`语言模型`和`索引模型`,并自动跳转模型配置页面。系统必须至少有这两类模型才能正常使用。 +- 如果系统未正常跳转,可以在`账号-模型提供商`页面,进行模型配置。[点击查看相关教程](/docs/development/modelconfig/ai-proxy) +- 目前已知可能问题:首次进入系统后,整个浏览器 tab 无法响应。此时需要删除该tab,重新打开一次即可。 ## FAQ +### 登录系统后,浏览器无法响应 + +无法点击任何内容,刷新也无效。此时需要删除该tab,重新打开一次即可。 + ### Mongo 副本集自动初始化失败 最新的 docker-compose 示例优化 Mongo 副本集初始化,实现了全自动。目前在 unbuntu20,22 centos7, wsl2, mac, window 均通过测试。仍无法正常启动,大部分是因为 cpu 不支持 AVX 指令集,可以切换 Mongo4.x 版本。 diff --git a/docSite/content/zh-cn/docs/development/intro.md b/docSite/content/zh-cn/docs/development/intro.md index 8c38a77d0ef8..d1ec79ba038f 100644 --- a/docSite/content/zh-cn/docs/development/intro.md +++ b/docSite/content/zh-cn/docs/development/intro.md @@ -70,6 +70,7 @@ Mongo 数据库需要注意,需要注意在连接地址中增加 `directConnec - `vectorMaxProcess`: 向量生成最大进程,根据数据库和 key 的并发数来决定,通常单个 120 号,2c4g 服务器设置 10~15。 - `qaMaxProcess`: QA 生成最大进程 +- `vlmMaxProcess`: 图片理解模型最大进程 - `pgHNSWEfSearch`: PostgreSQL vector 索引参数,越大搜索精度越高但是速度越慢,具体可看 pgvector 官方说明。 ### 5. 运行 diff --git a/docSite/content/zh-cn/docs/development/migration/docker_db.md b/docSite/content/zh-cn/docs/development/migration/docker_db.md index 019531353f2a..4b34e4f5c36c 100644 --- a/docSite/content/zh-cn/docs/development/migration/docker_db.md +++ b/docSite/content/zh-cn/docs/development/migration/docker_db.md @@ -7,9 +7,18 @@ draft: false images: [] --- -## Copy文件 +## 1. 停止服务 + +```bash +docker-compose down +``` + + +## 2. Copy文件夹 Docker 部署数据库都会通过 volume 挂载本地的目录进入容器,如果要迁移,直接复制这些目录即可。 `PG 数据`: pg/data -`Mongo 数据`: mongo/data \ No newline at end of file +`Mongo 数据`: mongo/data + +直接把pg 和 mongo目录全部复制走即可。 \ No newline at end of file diff --git a/docSite/content/zh-cn/docs/development/modelConfig/ai-proxy.md b/docSite/content/zh-cn/docs/development/modelConfig/ai-proxy.md new file mode 100644 index 000000000000..5d86c095788e --- /dev/null +++ b/docSite/content/zh-cn/docs/development/modelConfig/ai-proxy.md @@ -0,0 +1,129 @@ +--- +title: '通过 AI Proxy 接入模型' +description: '通过 AI Proxy 接入模型' +icon: 'api' +draft: false +toc: true +weight: 744 +--- + +从 `FastGPT 4.8.23` 版本开始,引入 AI Proxy 来进一步方便模型的配置。 + +AI Proxy 与 One API 类似,也是作为一个 OpenAI 接口管理 & 分发系统,可以通过标准的 OpenAI API 格式访问所有的大模型,开箱即用。 + +## 部署 + +### Docker 版本 + +`docker-compose.yml` 文件已加入了 AI Proxy 配置,可直接使用。[点击查看最新的 yml 配置](https://raw.githubusercontent.com/labring/FastGPT/main/deploy/docker/docker-compose-pgvector.yml) + +从旧版升级的用户,可以复制 yml 里,ai proxy 的配置,加入到旧的 yml 文件中。 + +## 运行原理 + +AI proxy 核心模块: + +1. 渠道管理:管理各家模型提供商的 API Key 和可用模型列表。 +2. 模型调用:根据请求的模型,选中对应的渠道;根据渠道的 API 格式,构造请求体,发送请求;格式化响应体成标准格式返回。 +3. 调用日志:详细记录模型调用的日志,并在错误时候可以记录其入参和报错信息,方便排查。 + +运行流程: + +![aiproxy12](/imgs/aiproxy1.png) + +## 在 FastGPT 中使用 + +AI proxy 相关功能,可以在`账号-模型提供商`页面找到。 + +### 1. 创建渠道 + +在`模型提供商`的配置页面,点击`模型渠道`,进入渠道配置页面 + +![aiproxy1](/imgs/aiproxy-1.png) + +点击右上角的“新增渠道”,即可进入渠道配置页面 + +![aiproxy2](/imgs/aiproxy-2.png) + +以阿里云的模型为例,进行如下配置 + +![aiproxy3](/imgs/aiproxy-3.png) + +1. 渠道名:展示在外部的渠道名称,仅作标识; +2. 厂商:模型对应的厂商,不同厂商对应不同的默认地址和 API 密钥格式; +3. 模型:当前渠道具体可以使用的模型,系统内置了主流的一些模型,如果下拉框中没有想要的选项,可以点击“新增模型”,[增加自定义模型](/docs/development/modelconfig/intro/#新增自定义模型); +4. 模型映射:将 FastGPT 请求的模型,映射到具体提供的模型上。例如: + +```json +{ + "gpt-4o-test": "gpt-4o", +} +``` + +FatGPT 中的模型为 `gpt-4o-test`,向 AI Proxy 发起请求时也是 `gpt-4o-test`。AI proxy 在向上游发送请求时,实际的`model`为 `gpt-4o`。 + +5. 代理地址:具体请求的地址,系统给每个主流渠道配置了默认的地址,如果无需改动则不用填。 +6. API 密钥:从模型厂商处获取的 API 凭证。注意部分厂商需要提供多个密钥组合,可以根据提示进行输入。 + +最后点击“新增”,就能在“模型渠道”下看到刚刚配置的渠道 + +![aiproxy4](/imgs/aiproxy-4.png) + +### 2. 渠道测试 + +然后可以对渠道进行测试,确保配置的模型有效 + +![aiproxy5](/imgs/aiproxy-5.png) + +点击“模型测试”,可以看到配置的模型列表,点击“开始测试” + +![aiproxy6](/imgs/aiproxy-6.png) + +等待模型测试完成后,会输出每个模型的测试结果以及请求时长 + +![aiproxy7](/imgs/aiproxy-7.png) + +### 3. 启用模型 + +最后在`模型配置`中,可以选择启用对应的模型,这样就能在平台中使用了,更多模型配置可以参考[模型配置](/docs/development/modelconfig/intro) + +![aiproxy8](/imgs/aiproxy-8.png) + +## 其他功能介绍 + +### 优先级 + +范围1~100。数值越大,越容易被优先选中。 + +![aiproxy9](/imgs/aiproxy-9.png) + +### 启用/禁用 + +在渠道右侧的控制菜单中,还可以控制渠道的启用或禁用,被禁用的渠道将无法再提供模型服务 + +![aiproxy10](/imgs/aiproxy-10.png) + +### 调用日志 + +在 `调用日志` 页面,会展示发送到模型处的请求记录,包括具体的输入输出 tokens、请求时间、请求耗时、请求地址等等。错误的请求,则会详细的入参和错误信息,方便排查,但仅会保留 1 小时(环境变量里可配置)。 + +![aiproxy11](/imgs/aiproxy-11.png) + +## 从 OneAPI 迁移到 AI Proxy + +可以从任意终端,发起 1 个 HTTP 请求。其中 `{{host}}` 替换成 AI Proxy 地址,`{{admin_key}}` 替换成 AI Proxy 中 `ADMIN_KEY` 的值。 + +Body 参数 `dsn` 为 OneAPI 的 mysql 连接串。 + +```bash +curl --location --request POST '{{host}}/api/channels/import/oneapi' \ +--header 'Authorization: Bearer {{admin_key}}' \ +--header 'Content-Type: application/json' \ +--data-raw '{ + "dsn": "mysql://root:s5mfkwst@tcp(dbconn.sealoshzh.site:33123)/mydb" +}' +``` + +执行成功的情况下会返回 "success": true + +脚本目前不是完全准,仅是简单的做数据映射,主要是迁移`代理地址`、`模型`和`API 密钥`,建议迁移后再进行手动检查。 \ No newline at end of file diff --git a/docSite/content/zh-cn/docs/development/modelConfig/intro.md b/docSite/content/zh-cn/docs/development/modelConfig/intro.md index 12e8f9841c6a..446eaae9e55b 100644 --- a/docSite/content/zh-cn/docs/development/modelConfig/intro.md +++ b/docSite/content/zh-cn/docs/development/modelConfig/intro.md @@ -13,9 +13,15 @@ weight: 744 ## 配置模型 -### 1. 使用 OneAPI 对接模型提供商 +### 1. 对接模型提供商 -可以使用 [OneAPI 接入教程](/docs/development/modelconfig/one-api) 来进行模型聚合,从而可以对接更多模型提供商。你需要先在各服务商申请好 API 接入 OneAPI 后,才能在 FastGPT 中使用这些模型。示例流程如下: +#### AI Proxy + +从 4.8.23 版本开始, FastGPT 支持在页面上配置模型提供商,即使用 [AI Proxy 接入教程](/docs/development/modelconfig/ai-proxy) 来进行模型聚合,从而可以对接更多模型提供商。 + +#### One API + +也可以使用 [OneAPI 接入教程](/docs/development/modelconfig/one-api)。你需要先在各服务商申请好 API 接入 OneAPI 后,才能在 FastGPT 中使用这些模型。示例流程如下: ![alt text](/imgs/image-95.png) @@ -23,22 +29,12 @@ weight: 744 {{% alert icon=" " context="info" %}} - [SiliconCloud(硅基流动)](https://cloud.siliconflow.cn/i/TR9Ym0c4): 提供开源模型调用的平台。 -- [Sealos AIProxy](https://hzh.sealos.run/?openapp=system-aiproxy): 提供国内各家模型代理,无需逐一申请 api。 +- [Sealos AIProxy](https://cloud.sealos.run/?uid=fnWRt09fZP&openapp=system-aiproxy): 提供国内各家模型代理,无需逐一申请 api。 {{% /alert %}} 在 OneAPI 配置好模型后,你就可以打开 FastGPT 页面,启用对应模型了。 -### 2. 登录 root 用户 - -仅 root 用户可以进行模型配置。 - -### 3. 进入模型配置页面 - -登录 root 用户后,在`账号-模型提供商-模型配置`中,你可以看到所有内置的模型和自定义模型,以及哪些模型启用了。 - -![alt text](/image-90.png) - -### 4. 配置介绍 +### 2. 配置介绍 {{% alert icon="🤖 " context="success" %}} 注意: @@ -467,4 +463,4 @@ OneAPI 的语言识别接口,无法正确的识别其他模型(会始终识 "charsPointsPrice": 0 } } -``` \ No newline at end of file +``` diff --git a/docSite/content/zh-cn/docs/development/modelConfig/one-api.md b/docSite/content/zh-cn/docs/development/modelConfig/one-api.md index 537e90067c0a..7d829bc22b25 100644 --- a/docSite/content/zh-cn/docs/development/modelConfig/one-api.md +++ b/docSite/content/zh-cn/docs/development/modelConfig/one-api.md @@ -20,10 +20,6 @@ FastGPT 目前采用模型分离的部署方案,FastGPT 中只兼容 OpenAI ## 部署 -### Docker 版本 - -`docker-compose.yml` 文件已加入了 OneAPI 配置,可直接使用。默认暴露在 3001 端口。 - ### Sealos 版本 * 北京区: [点击部署 OneAPI](https://hzh.sealos.run/?openapp=system-template%3FtemplateName%3Done-api) diff --git a/docSite/content/zh-cn/docs/development/modelConfig/siliconCloud.md b/docSite/content/zh-cn/docs/development/modelConfig/siliconCloud.md index 06036deb3adf..2f958b510b51 100644 --- a/docSite/content/zh-cn/docs/development/modelConfig/siliconCloud.md +++ b/docSite/content/zh-cn/docs/development/modelConfig/siliconCloud.md @@ -35,7 +35,7 @@ CHAT_API_KEY=sk-xxxxxx ![alt text](/imgs/image-104.png) -## 5. 体验测试 +## 4. 体验测试 ### 测试对话和图片识别 diff --git a/docSite/content/zh-cn/docs/development/openapi/dataset.md b/docSite/content/zh-cn/docs/development/openapi/dataset.md index 582d808329ea..d43b2026d293 100644 --- a/docSite/content/zh-cn/docs/development/openapi/dataset.md +++ b/docSite/content/zh-cn/docs/development/openapi/dataset.md @@ -297,7 +297,9 @@ curl --location --request DELETE 'http://localhost:3000/api/core/dataset/delete? | --- | --- | --- | | datasetId | 知识库ID | ✅ | | parentId: | 父级ID,不填则默认为根目录 | | -| trainingType | 训练模式。chunk: 按文本长度进行分割;qa: QA拆分;auto: 增强训练 | ✅ | +| trainingType | 数据处理方式。chunk: 按文本长度进行分割;qa: 问答对提取 | ✅ | +| autoIndexes | 是否自动生成索引(仅商业版支持) | | +| imageIndex | 是否自动生成图片索引(仅商业版支持) | | | chunkSize | 预估块大小 | | | chunkSplitter | 自定义最高优先分割符号 | | | qaPrompt | qa拆分提示词 | | @@ -1061,10 +1063,12 @@ curl --location --request DELETE 'http://localhost:3000/api/core/dataset/collect | 字段 | 类型 | 说明 | 必填 | | --- | --- | --- | --- | -| defaultIndex | Boolean | 是否为默认索引 | ✅ | -| dataId | String | 关联的向量ID | ✅ | +| type | String | 可选索引类型:default-默认索引; custom-自定义索引; summary-总结索引; question-问题索引; image-图片索引 | | +| dataId | String | 关联的向量ID,变更数据时候传入该 ID,会进行差量更新,而不是全量更新 | | | text | String | 文本内容 | ✅ | +`type` 不填则默认为 `custom` 索引,还会基于 q/a 组成一个默认索引。如果传入了默认索引,则不会额外创建。 + ### 为集合批量添加添加数据 注意,每次最多推送 200 组数据。 @@ -1079,7 +1083,7 @@ curl --location --request POST 'https://api.fastgpt.in/api/core/dataset/data/pus --header 'Content-Type: application/json' \ --data-raw '{     "collectionId": "64663f451ba1676dbdef0499", - "trainingMode": "chunk", + "trainingType": "chunk", "prompt": "可选。qa 拆分引导词,chunk 模式下忽略", "billId": "可选。如果有这个值,本次的数据会被聚合到一个订单中,这个值可以重复使用。可以参考 [创建训练订单] 获取该值。",     "data": [ @@ -1296,8 +1300,7 @@ curl --location --request GET 'http://localhost:3000/api/core/dataset/data/detai "chunkIndex": 0, "indexes": [ { - "defaultIndex": true, - "type": "chunk", + "type": "default", "dataId": "3720083", "text": "N o . 2 0 2 2 1 2中 国 信 息 通 信 研 究 院京东探索研究院2022年 9月人工智能生成内容(AIGC)白皮书(2022 年)版权声明本白皮书版权属于中国信息通信研究院和京东探索研究院,并受法律保护。转载、摘编或利用其它方式使用本白皮书文字或者观点的,应注明“来源:中国信息通信研究院和京东探索研究院”。违反上述声明者,编者将追究其相关法律责任。前 言习近平总书记曾指出,“数字技术正以新理念、新业态、新模式全面融入人类经济、政治、文化、社会、生态文明建设各领域和全过程”。在当前数字世界和物理世界加速融合的大背景下,人工智能生成内容(Artificial Intelligence Generated Content,简称 AIGC)正在悄然引导着一场深刻的变革,重塑甚至颠覆数字内容的生产方式和消费模式,将极大地丰富人们的数字生活,是未来全面迈向数字文明新时代不可或缺的支撑力量。", "_id": "65abd4b29d1448617cba61dc" @@ -1332,13 +1335,19 @@ curl --location --request PUT 'http://localhost:3000/api/core/dataset/data/updat "q":"测试111", "a":"sss", "indexes":[ + { + "dataId": "xxxx", + "type": "default", + "text": "默认索引" + }, { "dataId": "xxx", - "defaultIndex":false, - "text":"自定义索引1" + "type": "custom", + "text": "旧的自定义索引1" }, { - "text":"修改后的自定义索引2。(会删除原来的自定义索引2,并插入新的自定义索引2)" + "type":"custom", + "text":"新增的自定义索引" } ] }' diff --git a/docSite/content/zh-cn/docs/development/upgrading/482.md b/docSite/content/zh-cn/docs/development/upgrading/482.md index f50d22dd9bb4..f59ba31fbb19 100644 --- a/docSite/content/zh-cn/docs/development/upgrading/482.md +++ b/docSite/content/zh-cn/docs/development/upgrading/482.md @@ -20,7 +20,7 @@ SANDBOX_URL=内网地址 ## Docker 部署 -可以拉取最新 [docker-compose.yml](https://github.com/labring/FastGPT/blob/main/files/docker/docker-compose.yml) 文件参考 +可以拉取最新 [docker-compose.yml](https://github.com/labring/FastGPT/blob/main/deploy/docker/docker-compose.yml) 文件参考 1. 新增一个容器 `sandbox` 2. fastgpt 和 fastgpt-pro(商业版) 容器新增环境变量: `SANDBOX_URL` diff --git a/docSite/content/zh-cn/docs/development/upgrading/4820.md b/docSite/content/zh-cn/docs/development/upgrading/4820.md index d51bb8dd9145..e6fb621c9e84 100644 --- a/docSite/content/zh-cn/docs/development/upgrading/4820.md +++ b/docSite/content/zh-cn/docs/development/upgrading/4820.md @@ -35,7 +35,7 @@ curl --location --request POST 'https://{{host}}/api/admin/initv4820' \ ## 完整更新内容 -1. 新增 - 可视化模型参数配置,取代原配置文件配置模型。预设超过 100 个模型配置。同时支持所有类型模型的一键测试。(预计下个版本会完全支持在页面上配置渠道)。 +1. 新增 - 可视化模型参数配置,取代原配置文件配置模型。预设超过 100 个模型配置。同时支持所有类型模型的一键测试。(预计下个版本会完全支持在页面上配置渠道)。[点击查看模型配置方案](/docs/development/modelconfig/intro/) 2. 新增 - DeepSeek resoner 模型支持输出思考过程。 3. 新增 - 使用记录导出和仪表盘。 4. 新增 - markdown 语法扩展,支持音视频(代码块 audio 和 video)。 diff --git a/docSite/content/zh-cn/docs/development/upgrading/4823.md b/docSite/content/zh-cn/docs/development/upgrading/4823.md index e648393953fb..446245b1eb2f 100644 --- a/docSite/content/zh-cn/docs/development/upgrading/4823.md +++ b/docSite/content/zh-cn/docs/development/upgrading/4823.md @@ -1,5 +1,5 @@ --- -title: 'V4.8.23(进行中)' +title: 'V4.8.23' description: 'FastGPT V4.8.23 更新说明' icon: 'upgrade' draft: false @@ -7,10 +7,33 @@ toc: true weight: 802 --- +## 更新指南 + +### 1. 做好数据库备份 + +### 2. 更新镜像: + +- 更新 fastgpt 镜像 tag: v4.8.23-fix +- 更新 fastgpt-pro 商业版镜像 tag: v4.8.23-fix +- Sandbox 镜像无需更新 + +### 3. 运行升级脚本 + +从任意终端,发起 1 个 HTTP 请求。其中 {{rootkey}} 替换成环境变量里的 `rootkey`;{{host}} 替换成**FastGPT 域名**。 + +```bash +curl --location --request POST 'https://{{host}}/api/admin/initv4823' \ +--header 'rootkey: {{rootkey}}' \ +--header 'Content-Type: application/json' +``` + +脚本会清理一些知识库脏数据,主要是多余的全文索引。 ## 🚀 新增内容 1. 增加默认“知识库文本理解模型”配置 +2. AI proxy V1版,可替换 OneAPI使用,同时提供完整模型调用日志,便于排查问题。 +3. 增加工单入口支持。 ## ⚙️ 优化 @@ -18,8 +41,14 @@ weight: 802 2. 集合列表数据统计方式,提高大数据量统计性能。 3. 优化数学公式,转义 Latex 格式成 Markdown 格式。 4. 解析文档图片,图片太大时,自动忽略。 +5. 时间选择器,当天开始时间自动设0,结束设置设 23:59:59,避免 UI 与实际逻辑偏差。 +6. 升级 mongoose 库版本依赖。 ## 🐛 修复 1. 标签过滤时,子文件夹未成功过滤。 -2. 暂时移除 md 阅读优化,避免链接分割错误。 \ No newline at end of file +2. 暂时移除 md 阅读优化,避免链接分割错误。 +3. 离开团队时,未刷新成员列表。 +4. PPTX 编码错误,导致解析失败。 +5. 删除知识库单条数据时,全文索引未跟随删除。 +6. 修复 Mongo Dataset text 索引在查询数据时未生效。 \ No newline at end of file diff --git a/docSite/content/zh-cn/docs/development/upgrading/483.md b/docSite/content/zh-cn/docs/development/upgrading/483.md index 81f30a9500b3..924b4db8eb33 100644 --- a/docSite/content/zh-cn/docs/development/upgrading/483.md +++ b/docSite/content/zh-cn/docs/development/upgrading/483.md @@ -15,7 +15,7 @@ weight: 821 ## V4.8.3 更新说明 -1. 新增 - 支持 Milvus 数据库, 可参考最新的 [docker-compose-milvus.yml](https://github.com/labring/FastGPT/blob/main/files/docker/docker-compose-milvus.yml). +1. 新增 - 支持 Milvus 数据库, 可参考最新的 [docker-compose-milvus.yml](https://github.com/labring/FastGPT/blob/main/deploy/docker/docker-compose-milvus.yml). 2. 新增 - 给 chat 接口 empty answer 增加 log,便于排查模型问题。 3. 新增 - ifelse判断器,字符串支持正则。 4. 新增 - 代码运行支持 console.log 输出调试。 diff --git a/docSite/content/zh-cn/docs/development/upgrading/490.md b/docSite/content/zh-cn/docs/development/upgrading/490.md new file mode 100644 index 000000000000..eb654c1b506e --- /dev/null +++ b/docSite/content/zh-cn/docs/development/upgrading/490.md @@ -0,0 +1,190 @@ +--- +title: 'V4.9.0(包含升级脚本)' +description: 'FastGPT V4.9.0 更新说明' +icon: 'upgrade' +draft: false +toc: true +weight: 801 +--- + + +## 更新指南 + +### 1. 做好数据库备份 + +### 2. 更新镜像和 PG 容器 + +- 更新 FastGPT 镜像 tag: v4.9.0 +- 更新 FastGPT 商业版镜像 tag: v4.9.0 +- Sandbox 镜像,可以不更新 +- 更新 PG 容器为 v0.8.0-pg15, 可以查看[最新的 yml](https://raw.githubusercontent.com/labring/FastGPT/main/deploy/docker/docker-compose-pgvector.yml) + +### 3. 替换 OneAPI(可选) + +如果需要使用 AI Proxy 替换 OneAPI 的用户可执行该步骤。 + +#### 1. 修改 yml 文件 + +参考[最新的 yml](https://raw.githubusercontent.com/labring/FastGPT/main/deploy/docker/docker-compose-pgvector.yml) 文件。里面已移除 OneAPI 并添加了 AIProxy配置。包含一个服务和一个 PgSQL 数据库。将 `aiproxy` 的配置`追加`到 OneAPI 的配置后面(先不要删除 OneAPI,有一个初始化会自动同步 OneAPI 的配置) + +{{% details title="AI Proxy Yml 配置" closed="true" %}} + +``` + # AI Proxy + aiproxy: + image: 'ghcr.io/labring/sealos-aiproxy-service:latest' + container_name: aiproxy + restart: unless-stopped + depends_on: + aiproxy_pg: + condition: service_healthy + networks: + - fastgpt + environment: + # 对应 fastgpt 里的AIPROXY_API_TOKEN + - ADMIN_KEY=aiproxy + # 错误日志详情保存时间(小时) + - LOG_DETAIL_STORAGE_HOURS=1 + # 数据库连接地址 + - SQL_DSN=postgres://postgres:aiproxy@aiproxy_pg:5432/aiproxy + # 最大重试次数 + - RetryTimes=3 + # 不需要计费 + - BILLING_ENABLED=false + # 不需要严格检测模型 + - DISABLE_MODEL_CONFIG=true + healthcheck: + test: ['CMD', 'curl', '-f', 'http://localhost:3000/api/status'] + interval: 5s + timeout: 5s + retries: 10 + aiproxy_pg: + image: pgvector/pgvector:0.8.0-pg15 # docker hub + # image: registry.cn-hangzhou.aliyuncs.com/fastgpt/pgvector:v0.8.0-pg15 # 阿里云 + restart: unless-stopped + container_name: aiproxy_pg + volumes: + - ./aiproxy_pg:/var/lib/postgresql/data + networks: + - fastgpt + environment: + TZ: Asia/Shanghai + POSTGRES_USER: postgres + POSTGRES_DB: aiproxy + POSTGRES_PASSWORD: aiproxy + healthcheck: + test: ['CMD', 'pg_isready', '-U', 'postgres', '-d', 'aiproxy'] + interval: 5s + timeout: 5s + retries: 10 +``` + +{{% /details %}} + +#### 2. 增加 FastGPT 环境变量: + +修改 yml 文件中,fastgpt 容器的环境变量: + +``` +# AI Proxy 的地址,如果配了该地址,优先使用 +- AIPROXY_API_ENDPOINT=http://aiproxy:3000 +# AI Proxy 的 Admin Token,与 AI Proxy 中的环境变量 ADMIN_KEY +- AIPROXY_API_TOKEN=aiproxy +``` + +#### 3. 重载服务 + +`docker-compose down` 停止服务,然后 `docker-compose up -d` 启动服务,此时会追加 `aiproxy` 服务,并修改 FastGPT 的配置。 + +#### 4. 执行OneAPI迁移AI proxy脚本 + +- 可联网方案: + +```bash +# 进入 aiproxy 容器 +docker exec -it aiproxy sh +# 安装 curl +apk add curl +# 执行脚本 +curl --location --request POST 'http://localhost:3000/api/channels/import/oneapi' \ +--header 'Authorization: Bearer aiproxy' \ +--header 'Content-Type: application/json' \ +--data-raw '{ + "dsn": "mysql://root:oneapimmysql@tcp(mysql:3306)/oneapi" +}' +# 返回 {"data":[],"success":true} 代表成功 +``` + +- 无法联网时,可打开`aiproxy`的外网暴露端口,然后在本地执行脚本。 + +aiProxy 暴露端口:3003:3000,修改后重新 `docker-compose up -d` 启动服务。 + +```bash +# 在终端执行脚本 +curl --location --request POST 'http://localhost:3003/api/channels/import/oneapi' \ +--header 'Authorization: Bearer aiproxy' \ +--header 'Content-Type: application/json' \ +--data-raw '{ + "dsn": "mysql://root:oneapimmysql@tcp(mysql:3306)/oneapi" +}' +# 返回 {"data":[],"success":true} 代表成功 +``` + +- 如果不熟悉 docker 操作,建议不要走脚本迁移,直接删除 OneAPI 所有内容,然后手动重新添加渠道。 + +#### 5. 进入 FastGPT 检查`AI Proxy` 服务是否正常启动。 + +登录 root 账号后,在`账号-模型提供商`页面,可以看到多出了`模型渠道`和`调用日志`两个选项,打开模型渠道,可以看到之前 OneAPI 的渠道,说明迁移完成,此时可以手动再检查下渠道是否正常。 + +#### 6. 删除 OneAPI 服务 + +```bash +# 停止服务,或者针对性停止 OneAPI 和其 Mysql +docker-compose down +# yml 文件中删除 OneAPI 和其 Mysql 依赖 +# 重启服务 +docker-compose up -d +``` + +### 4. 运行 FastGPT 升级脚本 + +从任意终端,发起 1 个 HTTP 请求。其中 {{rootkey}} 替换成环境变量里的 `rootkey`;{{host}} 替换成**FastGPT 域名**。 + +```bash +curl --location --request POST 'https://{{host}}/api/admin/initv490' \ +--header 'rootkey: {{rootkey}}' \ +--header 'Content-Type: application/json' +``` + +**脚本功能** + +1. 升级 PG Vector 插件版本 +2. 全量更新知识库集合字段。 +3. 全量更新知识库数据中,index 的 type 类型。(时间较长,最后可能提示 timeout,可忽略,数据库不崩都会一直增量执行) + +## 兼容 & 弃用 + +1. 弃用 - 之前私有化部署的自定义文件解析方案,请同步更新到最新的配置方案。[点击查看 PDF 增强解析配置](/docs/development/configuration/#使用-doc2x-解析-pdf-文件) +2. 弃用 - 弃用旧版本地文件上传 API:/api/core/dataset/collection/create/file(以前仅商业版可用的 API,该接口已放切换成:/api/core/dataset/collection/create/localFile) +3. 停止维护,即将弃用 - 外部文件库相关 API,可通过 API 文件库替代。 +4. API更新 - 上传文件至知识库、创建连接集合、API 文件库、推送分块数据等带有 `trainingType` 字段的接口,`trainingType`字段未来仅支持`chunk`和`QA`两种模式。增强索引模式将设置单独字段:`autoIndexes`,目前仍有适配旧版`trainingType=auto`代码,但请尽快变更成新接口类型。具体可见:[知识库 OpenAPI 文档](/docs/development/openapi/dataset.md) + +## 🚀 新增内容 + +1. PDF增强解析交互添加到页面上。同时内嵌 Doc2x 服务,可直接使用 Doc2x 服务解析 PDF 文件。 +2. 图片自动标注,同时修改知识库文件上传部分数据逻辑和交互。 +3. pg vector 插件升级 0.8.0 版本,引入迭代搜索,减少部分数据无法被检索的情况。 +4. 新增 qwen-qwq 系列模型配置。 + +## ⚙️ 优化 + +1. 知识库数据不再限制索引数量,可无限自定义。同时可自动更新输入文本的索引,不影响自定义索引。 +2. Markdown 解析,增加链接后中文标点符号检测,增加空格。 +3. Prompt 模式工具调用,支持思考模型。同时优化其格式检测,减少空输出的概率。 +4. Mongo 文件读取流合并,减少计算量。同时优化存储 chunks,极大提高大文件读取速度。50M PDF 读取时间提高 3 倍。 +5. HTTP Body 适配,增加对字符串对象的适配。 + +## 🐛 修复 + +1. 增加网页抓取安全链接校验。 +2. 批量运行时,全局变量未进一步传递到下一次运行中,导致最终变量更新错误。 diff --git a/docSite/content/zh-cn/docs/use-cases/external-integration/official_account.md b/docSite/content/zh-cn/docs/use-cases/external-integration/official_account.md index c91a2c58c448..6f9a903bdb84 100644 --- a/docSite/content/zh-cn/docs/use-cases/external-integration/official_account.md +++ b/docSite/content/zh-cn/docs/use-cases/external-integration/official_account.md @@ -89,6 +89,9 @@ weight: 506 47.99.59.223 112.124.46.5 121.40.46.247 +120.26.145.73 +120.26.147.199 +121.43.125.163 ``` ## 4. 获取AES Key,选择加密方式 diff --git a/package.json b/package.json index 655f00e15812..31fd2fe8d050 100644 --- a/package.json +++ b/package.json @@ -11,16 +11,22 @@ "initIcon": "node ./scripts/icon/init.js", "previewIcon": "node ./scripts/icon/index.js", "api:gen": "tsc ./scripts/openapi/index.ts && node ./scripts/openapi/index.js && npx @redocly/cli build-docs ./scripts/openapi/openapi.json -o ./projects/app/public/openapi/index.html", - "create:i18n": "node ./scripts/i18n/index.js" + "create:i18n": "node ./scripts/i18n/index.js", + "test": "vitest run --exclude './projects/app/src/test/**'", + "test:all": "vitest run", + "test:workflow": "vitest run workflow" }, "devDependencies": { "@chakra-ui/cli": "^2.4.1", + "@vitest/coverage-v8": "^3.0.2", "husky": "^8.0.3", - "lint-staged": "^13.3.0", "i18next": "23.11.5", + "lint-staged": "^13.3.0", "next-i18next": "15.3.0", - "react-i18next": "14.1.2", "prettier": "3.2.4", + "react-i18next": "14.1.2", + "vitest": "^3.0.2", + "vitest-mongodb": "^1.0.1", "zhlint": "^0.7.4" }, "lint-staged": { diff --git a/packages/README.md b/packages/README.md new file mode 100644 index 000000000000..13253ba05620 --- /dev/null +++ b/packages/README.md @@ -0,0 +1,3 @@ +# 目录说明 + +该目录为 FastGPT 的依赖包,多端复用。 diff --git a/packages/global/common/error/code/dataset.ts b/packages/global/common/error/code/dataset.ts index cca81331f50f..a76eab58ab2b 100644 --- a/packages/global/common/error/code/dataset.ts +++ b/packages/global/common/error/code/dataset.ts @@ -4,6 +4,7 @@ import { ErrType } from '../errorCode'; /* dataset: 501000 */ export enum DatasetErrEnum { unExist = 'unExistDataset', + unExistCollection = 'unExistCollection', unAuthDataset = 'unAuthDataset', unCreateCollection = 'unCreateCollection', unAuthDatasetCollection = 'unAuthDatasetCollection', @@ -28,6 +29,10 @@ const datasetErr = [ statusText: DatasetErrEnum.unExist, message: 'core.dataset.error.unExistDataset' }, + { + statusText: DatasetErrEnum.unExistCollection, + message: i18nT('common:error_collection_not_exist') + }, { statusText: DatasetErrEnum.unAuthDataset, message: 'core.dataset.error.unAuthDataset' diff --git a/packages/global/common/fn/utils.ts b/packages/global/common/fn/utils.ts deleted file mode 100644 index 43a1cfc9a414..000000000000 --- a/packages/global/common/fn/utils.ts +++ /dev/null @@ -1,31 +0,0 @@ -export const retryRun = (fn: () => T, retry = 2): T => { - try { - return fn(); - } catch (error) { - if (retry > 0) { - return retryRun(fn, retry - 1); - } - throw error; - } -}; - -export const batchRun = async (arr: T[], fn: (arr: T) => any, batchSize = 10) => { - const batchArr = new Array(batchSize).fill(null); - const result: any[] = []; - - const batchFn = async () => { - const data = arr.shift(); - if (data) { - result.push(await fn(data)); - return batchFn(); - } - }; - - await Promise.all( - batchArr.map(async () => { - await batchFn(); - }) - ); - - return result; -}; diff --git a/packages/global/common/string/markdown.ts b/packages/global/common/string/markdown.ts index 410ca4c75600..43a6e895a84f 100644 --- a/packages/global/common/string/markdown.ts +++ b/packages/global/common/string/markdown.ts @@ -1,4 +1,4 @@ -import { batchRun } from '../fn/utils'; +import { batchRun } from '../system/utils'; import { getNanoid, simpleText } from './tools'; import type { ImageType } from '../../../service/worker/readFile/type'; @@ -37,6 +37,80 @@ export const simpleMarkdownText = (rawText: string) => { return rawText.trim(); }; +export const htmlTable2Md = (content: string): string => { + return content.replace(/[\s\S]*?<\/table>/g, (htmlTable) => { + try { + // Clean up whitespace and newlines + const cleanHtml = htmlTable.replace(/\n\s*/g, ''); + const rows = cleanHtml.match(/(.*?)<\/tr>/g); + if (!rows) return htmlTable; + + // Parse table data + let tableData: string[][] = []; + let maxColumns = 0; + + // Try to convert to markdown table + rows.forEach((row, rowIndex) => { + if (!tableData[rowIndex]) { + tableData[rowIndex] = []; + } + let colIndex = 0; + const cells = row.match(/(.*?)<\/td>/g) || []; + + cells.forEach((cell) => { + while (tableData[rowIndex][colIndex]) { + colIndex++; + } + const colspan = parseInt(cell.match(/colspan="(\d+)"/)?.[1] || '1'); + const rowspan = parseInt(cell.match(/rowspan="(\d+)"/)?.[1] || '1'); + const content = cell.replace(/|<\/td>/g, '').trim(); + + for (let i = 0; i < rowspan; i++) { + for (let j = 0; j < colspan; j++) { + if (!tableData[rowIndex + i]) { + tableData[rowIndex + i] = []; + } + tableData[rowIndex + i][colIndex + j] = i === 0 && j === 0 ? content : '^^'; + } + } + colIndex += colspan; + maxColumns = Math.max(maxColumns, colIndex); + }); + + for (let i = 0; i < maxColumns; i++) { + if (!tableData[rowIndex][i]) { + tableData[rowIndex][i] = ' '; + } + } + }); + const chunks: string[] = []; + + const headerCells = tableData[0] + .slice(0, maxColumns) + .map((cell) => (cell === '^^' ? ' ' : cell || ' ')); + const headerRow = '| ' + headerCells.join(' | ') + ' |'; + chunks.push(headerRow); + + const separator = '| ' + Array(headerCells.length).fill('---').join(' | ') + ' |'; + chunks.push(separator); + + tableData.slice(1).forEach((row) => { + const paddedRow = row + .slice(0, maxColumns) + .map((cell) => (cell === '^^' ? ' ' : cell || ' ')); + while (paddedRow.length < maxColumns) { + paddedRow.push(' '); + } + chunks.push('| ' + paddedRow.join(' | ') + ' |'); + }); + + return chunks.join('\n'); + } catch (error) { + return htmlTable; + } + }); +}; + /** * format markdown * 1. upload base64 @@ -94,7 +168,7 @@ export const markdownProcess = async ({ return simpleMarkdownText(imageProcess); }; -export const matchMdImgTextAndUpload = (text: string) => { +export const matchMdImg = (text: string) => { const base64Regex = /!\[([^\]]*)\]\((data:image\/[^;]+;base64[^)]+)\)/g; const imageList: ImageType[] = []; diff --git a/packages/global/common/string/time.ts b/packages/global/common/string/time.ts index 51f6b2370566..87b612fd3aa4 100644 --- a/packages/global/common/string/time.ts +++ b/packages/global/common/string/time.ts @@ -7,12 +7,14 @@ import { i18nT } from '../../../web/i18n/utils'; dayjs.extend(utc); dayjs.extend(timezone); -export const formatTime2YMDHMW = (time?: Date) => dayjs(time).format('YYYY-MM-DD HH:mm:ss dddd'); -export const formatTime2YMDHMS = (time?: Date) => +export const formatTime2YMDHMW = (time?: Date | number) => + dayjs(time).format('YYYY-MM-DD HH:mm:ss dddd'); +export const formatTime2YMDHMS = (time?: Date | number) => time ? dayjs(time).format('YYYY-MM-DD HH:mm:ss') : ''; -export const formatTime2YMDHM = (time?: Date) => +export const formatTime2YMDHM = (time?: Date | number) => time ? dayjs(time).format('YYYY-MM-DD HH:mm') : ''; -export const formatTime2YMD = (time?: Date) => (time ? dayjs(time).format('YYYY-MM-DD') : ''); +export const formatTime2YMD = (time?: Date | number) => + time ? dayjs(time).format('YYYY-MM-DD') : ''; export const formatTime2HM = (time: Date = new Date()) => dayjs(time).format('HH:mm'); /** diff --git a/packages/global/common/system/types/index.d.ts b/packages/global/common/system/types/index.d.ts index 892beb4802a9..a2b2367c10cf 100644 --- a/packages/global/common/system/types/index.d.ts +++ b/packages/global/common/system/types/index.d.ts @@ -43,10 +43,14 @@ export type FastGPTConfigFileType = { export type FastGPTFeConfigsType = { show_workorder?: boolean; show_emptyChat?: boolean; + isPlus?: boolean; register_method?: ['email' | 'phone' | 'sync']; login_method?: ['email' | 'phone']; // Attention: login method is diffrent with oauth find_password_method?: ['email' | 'phone']; bind_notification_method?: ['email' | 'phone']; + googleClientVerKey?: string; + + show_emptyChat?: boolean; show_appStore?: boolean; show_git?: boolean; show_pay?: boolean; @@ -54,17 +58,22 @@ export type FastGPTFeConfigsType = { show_promotion?: boolean; show_team_chat?: boolean; show_compliance_copywriting?: boolean; + show_aiproxy?: boolean; concatMd?: string; + concatMd?: string; docUrl?: string; openAPIDocUrl?: string; systemPluginCourseUrl?: string; appTemplateCourse?: string; + customApiDomain?: string; + customSharePageDomain?: string; systemTitle?: string; systemDescription?: string; - googleClientVerKey?: string; - isPlus?: boolean; + scripts?: { [key: string]: string }[]; + favicon?: string; + sso?: { icon?: string; title?: string; @@ -90,13 +99,14 @@ export type FastGPTFeConfigsType = { exportDatasetLimitMinutes?: number; websiteSyncLimitMinuted?: number; }; - scripts?: { [key: string]: string }[]; - favicon?: string; - customApiDomain?: string; - customSharePageDomain?: string; uploadFileMaxAmount?: number; uploadFileMaxSize?: number; + + // Compute by systemEnv.customPdfParse + showCustomPdfParse?: boolean; + customPdfParsePrice?: number; + lafEnv?: string; navbarItems?: NavbarItemType[]; externalProviderWorkflowVariables?: ExternalProviderWorkflowVarType[]; @@ -106,9 +116,18 @@ export type SystemEnvType = { openapiPrefix?: string; vectorMaxProcess: number; qaMaxProcess: number; + vlmMaxProcess: number; pgHNSWEfSearch: number; tokenWorkers: number; // token count max worker oneapiUrl?: string; chatApiKey?: string; + + customPdfParse?: { + url?: string; + key?: string; + + doc2xKey?: string; + price?: number; // n points/1 page + }; }; diff --git a/packages/global/common/system/utils.ts b/packages/global/common/system/utils.ts index 8f79cb2f5cb8..e58761f4830c 100644 --- a/packages/global/common/system/utils.ts +++ b/packages/global/common/system/utils.ts @@ -16,3 +16,24 @@ export const retryFn = async (fn: () => Promise, retryTimes = 3): Promise< return Promise.reject(error); } }; + +export const batchRun = async (arr: T[], fn: (arr: T) => any, batchSize = 10) => { + const batchArr = new Array(batchSize).fill(null); + const result: any[] = []; + + const batchFn = async () => { + const data = arr.shift(); + if (data) { + result.push(await fn(data)); + return batchFn(); + } + }; + + await Promise.all( + batchArr.map(async () => { + await batchFn(); + }) + ); + + return result; +}; diff --git a/packages/global/core/ai/model.ts b/packages/global/core/ai/model.ts index 8fd818307a9e..314287629e4c 100644 --- a/packages/global/core/ai/model.ts +++ b/packages/global/core/ai/model.ts @@ -22,7 +22,7 @@ export const defaultQAModels: LLMModelItemType[] = [ maxTemperature: 1.2, charsPointsPrice: 0, censor: false, - vision: false, + vision: true, datasetProcess: true, toolChoice: true, functionCall: false, @@ -59,10 +59,17 @@ export const defaultSTTModels: STTModelType[] = [ export const getModelFromList = ( modelList: { provider: ModelProviderIdType; name: string; model: string }[], model: string -) => { +): + | { + avatar: string; + provider: ModelProviderIdType; + name: string; + model: string; + } + | undefined => { const modelData = modelList.find((item) => item.model === model) ?? modelList[0]; if (!modelData) { - throw new Error('No Key model is configured'); + return; } const provider = getModelProvider(modelData.provider); return { diff --git a/packages/global/core/app/type.d.ts b/packages/global/core/app/type.d.ts index 52b6964fb273..1a0a53b85f19 100644 --- a/packages/global/core/app/type.d.ts +++ b/packages/global/core/app/type.d.ts @@ -188,6 +188,7 @@ export type AppAutoExecuteConfigType = { // File export type AppFileSelectConfigType = { canSelectFile: boolean; + customPdfParse?: boolean; canSelectImg: boolean; maxFiles: number; }; diff --git a/packages/global/core/app/utils.ts b/packages/global/core/app/utils.ts index e6ef98a68fda..0835dbb59e54 100644 --- a/packages/global/core/app/utils.ts +++ b/packages/global/core/app/utils.ts @@ -70,6 +70,26 @@ export const appWorkflow2Form = ({ node.inputs, NodeInputKeyEnum.history ); + defaultAppForm.aiSettings.aiChatReasoning = findInputValueByKey( + node.inputs, + NodeInputKeyEnum.aiChatReasoning + ); + defaultAppForm.aiSettings.aiChatTopP = findInputValueByKey( + node.inputs, + NodeInputKeyEnum.aiChatTopP + ); + defaultAppForm.aiSettings.aiChatStopSign = findInputValueByKey( + node.inputs, + NodeInputKeyEnum.aiChatStopSign + ); + defaultAppForm.aiSettings.aiChatResponseFormat = findInputValueByKey( + node.inputs, + NodeInputKeyEnum.aiChatResponseFormat + ); + defaultAppForm.aiSettings.aiChatJsonSchema = findInputValueByKey( + node.inputs, + NodeInputKeyEnum.aiChatJsonSchema + ); } else if (node.flowNodeType === FlowNodeTypeEnum.datasetSearchNode) { defaultAppForm.dataset.datasets = findInputValueByKey( node.inputs, diff --git a/packages/global/core/chat/adapt.ts b/packages/global/core/chat/adapt.ts index 4c4d65101905..4302ca757c6b 100644 --- a/packages/global/core/chat/adapt.ts +++ b/packages/global/core/chat/adapt.ts @@ -1,8 +1,11 @@ import type { + AIChatItemValueItemType, ChatItemType, ChatItemValueItemType, RuntimeUserPromptType, - UserChatItemType + SystemChatItemValueItemType, + UserChatItemType, + UserChatItemValueItemType } from '../../core/chat/type.d'; import { ChatFileTypeEnum, ChatItemValueTypeEnum, ChatRoleEnum } from '../../core/chat/constants'; import type { @@ -174,147 +177,171 @@ export const GPTMessages2Chats = ( ): ChatItemType[] => { const chatMessages = messages .map((item) => { - const value: ChatItemType['value'] = []; const obj = GPT2Chat[item.role]; - if ( - obj === ChatRoleEnum.System && - item.role === ChatCompletionRequestMessageRoleEnum.System - ) { - if (Array.isArray(item.content)) { - item.content.forEach((item) => [ - value.push({ - type: ChatItemValueTypeEnum.text, - text: { - content: item.text - } - }) - ]); - } else { - value.push({ - type: ChatItemValueTypeEnum.text, - text: { - content: item.content - } - }); - } - } else if ( - obj === ChatRoleEnum.Human && - item.role === ChatCompletionRequestMessageRoleEnum.User - ) { - if (typeof item.content === 'string') { - value.push({ - type: ChatItemValueTypeEnum.text, - text: { - content: item.content - } - }); - } else if (Array.isArray(item.content)) { - item.content.forEach((item) => { - if (item.type === 'text') { + const value = (() => { + if ( + obj === ChatRoleEnum.System && + item.role === ChatCompletionRequestMessageRoleEnum.System + ) { + const value: SystemChatItemValueItemType[] = []; + + if (Array.isArray(item.content)) { + item.content.forEach((item) => [ value.push({ type: ChatItemValueTypeEnum.text, text: { content: item.text } - }); - } else if (item.type === 'image_url') { - value.push({ - //@ts-ignore - type: ChatItemValueTypeEnum.file, - file: { - type: ChatFileTypeEnum.image, - name: '', - url: item.image_url.url - } - }); - } else if (item.type === 'file_url') { - value.push({ - // @ts-ignore - type: ChatItemValueTypeEnum.file, - file: { - type: ChatFileTypeEnum.file, - name: item.name, - url: item.url - } - }); - } - }); - } - } else if ( - obj === ChatRoleEnum.AI && - item.role === ChatCompletionRequestMessageRoleEnum.Assistant - ) { - if (item.tool_calls && reserveTool) { - // save tool calls - const toolCalls = item.tool_calls as ChatCompletionMessageToolCall[]; - value.push({ - //@ts-ignore - type: ChatItemValueTypeEnum.tool, - tools: toolCalls.map((tool) => { - let toolResponse = - messages.find( - (msg) => - msg.role === ChatCompletionRequestMessageRoleEnum.Tool && - msg.tool_call_id === tool.id - )?.content || ''; - toolResponse = - typeof toolResponse === 'string' ? toolResponse : JSON.stringify(toolResponse); - - return { - id: tool.id, - toolName: tool.toolName || '', - toolAvatar: tool.toolAvatar || '', - functionName: tool.function.name, - params: tool.function.arguments, - response: toolResponse as string - }; - }) - }); - } else if (item.function_call && reserveTool) { - const functionCall = item.function_call as ChatCompletionMessageFunctionCall; - const functionResponse = messages.find( - (msg) => - msg.role === ChatCompletionRequestMessageRoleEnum.Function && - msg.name === item.function_call?.name - ) as ChatCompletionFunctionMessageParam; - - if (functionResponse) { + }) + ]); + } else { value.push({ - //@ts-ignore - type: ChatItemValueTypeEnum.tool, - tools: [ - { - id: functionCall.id || '', - toolName: functionCall.toolName || '', - toolAvatar: functionCall.toolAvatar || '', - functionName: functionCall.name, - params: functionCall.arguments, - response: functionResponse.content || '' - } - ] + type: ChatItemValueTypeEnum.text, + text: { + content: item.content + } }); } - } else if (item.interactive) { - value.push({ - //@ts-ignore - type: ChatItemValueTypeEnum.interactive, - interactive: item.interactive - }); - } else if (typeof item.content === 'string') { - const lastValue = value[value.length - 1]; - if (lastValue && lastValue.type === ChatItemValueTypeEnum.text && lastValue.text) { - lastValue.text.content += item.content; - } else { + return value; + } else if ( + obj === ChatRoleEnum.Human && + item.role === ChatCompletionRequestMessageRoleEnum.User + ) { + const value: UserChatItemValueItemType[] = []; + + if (typeof item.content === 'string') { value.push({ type: ChatItemValueTypeEnum.text, text: { content: item.content } }); + } else if (Array.isArray(item.content)) { + item.content.forEach((item) => { + if (item.type === 'text') { + value.push({ + type: ChatItemValueTypeEnum.text, + text: { + content: item.text + } + }); + } else if (item.type === 'image_url') { + value.push({ + //@ts-ignore + type: ChatItemValueTypeEnum.file, + file: { + type: ChatFileTypeEnum.image, + name: '', + url: item.image_url.url + } + }); + } else if (item.type === 'file_url') { + value.push({ + // @ts-ignore + type: ChatItemValueTypeEnum.file, + file: { + type: ChatFileTypeEnum.file, + name: item.name, + url: item.url + } + }); + } + }); } + return value; + } else if ( + obj === ChatRoleEnum.AI && + item.role === ChatCompletionRequestMessageRoleEnum.Assistant + ) { + const value: AIChatItemValueItemType[] = []; + + if (typeof item.reasoning_text === 'string') { + value.push({ + type: ChatItemValueTypeEnum.reasoning, + reasoning: { + content: item.reasoning_text + } + }); + } + if (item.tool_calls && reserveTool) { + // save tool calls + const toolCalls = item.tool_calls as ChatCompletionMessageToolCall[]; + value.push({ + //@ts-ignore + type: ChatItemValueTypeEnum.tool, + tools: toolCalls.map((tool) => { + let toolResponse = + messages.find( + (msg) => + msg.role === ChatCompletionRequestMessageRoleEnum.Tool && + msg.tool_call_id === tool.id + )?.content || ''; + toolResponse = + typeof toolResponse === 'string' ? toolResponse : JSON.stringify(toolResponse); + + return { + id: tool.id, + toolName: tool.toolName || '', + toolAvatar: tool.toolAvatar || '', + functionName: tool.function.name, + params: tool.function.arguments, + response: toolResponse as string + }; + }) + }); + } + if (item.function_call && reserveTool) { + const functionCall = item.function_call as ChatCompletionMessageFunctionCall; + const functionResponse = messages.find( + (msg) => + msg.role === ChatCompletionRequestMessageRoleEnum.Function && + msg.name === item.function_call?.name + ) as ChatCompletionFunctionMessageParam; + + if (functionResponse) { + value.push({ + //@ts-ignore + type: ChatItemValueTypeEnum.tool, + tools: [ + { + id: functionCall.id || '', + toolName: functionCall.toolName || '', + toolAvatar: functionCall.toolAvatar || '', + functionName: functionCall.name, + params: functionCall.arguments, + response: functionResponse.content || '' + } + ] + }); + } + } + if (item.interactive) { + value.push({ + //@ts-ignore + type: ChatItemValueTypeEnum.interactive, + interactive: item.interactive + }); + } + if (typeof item.content === 'string') { + const lastValue = value[value.length - 1]; + if (lastValue && lastValue.type === ChatItemValueTypeEnum.text && lastValue.text) { + lastValue.text.content += item.content; + } else { + value.push({ + type: ChatItemValueTypeEnum.text, + text: { + content: item.content + } + }); + } + } + + return value; } - } + + return []; + })(); return { dataId: item.dataId, diff --git a/packages/global/core/chat/type.d.ts b/packages/global/core/chat/type.d.ts index 4e010c68f749..8837075e8afc 100644 --- a/packages/global/core/chat/type.d.ts +++ b/packages/global/core/chat/type.d.ts @@ -77,6 +77,7 @@ export type AIChatItemValueItemType = { | ChatItemValueTypeEnum.reasoning | ChatItemValueTypeEnum.tool | ChatItemValueTypeEnum.interactive; + text?: { content: string; }; diff --git a/packages/global/core/dataset/api.d.ts b/packages/global/core/dataset/api.d.ts index 5cf6c860f3f3..99b4aaa3aa8d 100644 --- a/packages/global/core/dataset/api.d.ts +++ b/packages/global/core/dataset/api.d.ts @@ -1,5 +1,5 @@ import { DatasetDataIndexItemType, DatasetSchemaType } from './type'; -import { TrainingModeEnum, DatasetCollectionTypeEnum } from './constants'; +import { DatasetCollectionTypeEnum, DatasetCollectionDataProcessModeEnum } from './constants'; import type { LLMModelItemType } from '../ai/model.d'; import { ParentIdType } from 'common/parentFolder/type'; @@ -10,9 +10,11 @@ export type DatasetUpdateBody = { name?: string; avatar?: string; intro?: string; - agentModel?: LLMModelItemType; status?: DatasetSchemaType['status']; + agentModel?: string; + vlmModel?: string; + websiteConfig?: DatasetSchemaType['websiteConfig']; externalReadUrl?: DatasetSchemaType['externalReadUrl']; defaultPermission?: DatasetSchemaType['defaultPermission']; @@ -27,7 +29,10 @@ export type DatasetUpdateBody = { /* ================= collection ===================== */ export type DatasetCollectionChunkMetadataType = { parentId?: string; - trainingType?: TrainingModeEnum; + customPdfParse?: boolean; + trainingType?: DatasetCollectionDataProcessModeEnum; + imageIndex?: boolean; + autoIndexes?: boolean; chunkSize?: number; chunkSplitter?: string; qaPrompt?: string; @@ -131,9 +136,15 @@ export type PostWebsiteSyncParams = { export type PushDatasetDataProps = { collectionId: string; data: PushDatasetDataChunkProps[]; - trainingMode: TrainingModeEnum; + trainingType?: DatasetCollectionDataProcessModeEnum; + autoIndexes?: boolean; + imageIndex?: boolean; prompt?: string; + billId?: string; + + // Abandon + trainingMode?: DatasetCollectionDataProcessModeEnum; }; export type PushDatasetDataResponse = { insertLen: number; diff --git a/packages/global/core/dataset/collection/utils.ts b/packages/global/core/dataset/collection/utils.ts index f82a689c4128..a803a989f831 100644 --- a/packages/global/core/dataset/collection/utils.ts +++ b/packages/global/core/dataset/collection/utils.ts @@ -1,4 +1,4 @@ -import { DatasetCollectionTypeEnum, TrainingModeEnum, TrainingTypeMap } from '../constants'; +import { DatasetCollectionTypeEnum } from '../constants'; import { DatasetCollectionSchemaType } from '../type'; export const getCollectionSourceData = (collection?: DatasetCollectionSchemaType) => { @@ -16,9 +16,3 @@ export const getCollectionSourceData = (collection?: DatasetCollectionSchemaType export const checkCollectionIsFolder = (type: DatasetCollectionTypeEnum) => { return type === DatasetCollectionTypeEnum.folder || type === DatasetCollectionTypeEnum.virtual; }; - -export const getTrainingTypeLabel = (type?: TrainingModeEnum) => { - if (!type) return ''; - if (!TrainingTypeMap[type]) return ''; - return TrainingTypeMap[type].label; -}; diff --git a/packages/global/core/dataset/constants.ts b/packages/global/core/dataset/constants.ts index a522645f9d2f..7d0e3531bdd5 100644 --- a/packages/global/core/dataset/constants.ts +++ b/packages/global/core/dataset/constants.ts @@ -109,6 +109,26 @@ export const DatasetCollectionSyncResultMap = { } }; +export enum DatasetCollectionDataProcessModeEnum { + chunk = 'chunk', + qa = 'qa', + auto = 'auto' // abandon +} +export const DatasetCollectionDataProcessModeMap = { + [DatasetCollectionDataProcessModeEnum.chunk]: { + label: i18nT('common:core.dataset.training.Chunk mode'), + tooltip: i18nT('common:core.dataset.import.Chunk Split Tip') + }, + [DatasetCollectionDataProcessModeEnum.qa]: { + label: i18nT('common:core.dataset.training.QA mode'), + tooltip: i18nT('common:core.dataset.import.QA Import Tip') + }, + [DatasetCollectionDataProcessModeEnum.auto]: { + label: i18nT('common:core.dataset.training.Auto mode'), + tooltip: i18nT('common:core.dataset.training.Auto mode Tip') + } +}; + /* ------------ data -------------- */ /* ------------ training -------------- */ @@ -124,28 +144,11 @@ export enum ImportDataSourceEnum { export enum TrainingModeEnum { chunk = 'chunk', + qa = 'qa', auto = 'auto', - qa = 'qa' + image = 'image' } -export const TrainingTypeMap = { - [TrainingModeEnum.chunk]: { - label: i18nT('common:core.dataset.training.Chunk mode'), - tooltip: i18nT('common:core.dataset.import.Chunk Split Tip'), - openSource: true - }, - [TrainingModeEnum.auto]: { - label: i18nT('common:core.dataset.training.Auto mode'), - tooltip: i18nT('common:core.dataset.training.Auto mode Tip'), - openSource: false - }, - [TrainingModeEnum.qa]: { - label: i18nT('common:core.dataset.training.QA mode'), - tooltip: i18nT('common:core.dataset.import.QA Import Tip'), - openSource: true - } -}; - /* ------------ search -------------- */ export enum DatasetSearchModeEnum { embedding = 'embedding', diff --git a/packages/global/core/dataset/controller.d.ts b/packages/global/core/dataset/controller.d.ts index 732f4cf78f41..2382a94e61dc 100644 --- a/packages/global/core/dataset/controller.d.ts +++ b/packages/global/core/dataset/controller.d.ts @@ -20,9 +20,22 @@ export type UpdateDatasetDataProps = { })[]; }; -export type PatchIndexesProps = { - type: 'create' | 'update' | 'delete' | 'unChange'; - index: Omit & { - dataId?: string; - }; -}; +export type PatchIndexesProps = + | { + type: 'create'; + index: Omit & { + dataId?: string; + }; + } + | { + type: 'update'; + index: DatasetDataIndexItemType; + } + | { + type: 'delete'; + index: DatasetDataIndexItemType; + } + | { + type: 'unChange'; + index: DatasetDataIndexItemType; + }; diff --git a/packages/global/core/dataset/data/constants.ts b/packages/global/core/dataset/data/constants.ts new file mode 100644 index 000000000000..802b5f4693af --- /dev/null +++ b/packages/global/core/dataset/data/constants.ts @@ -0,0 +1,42 @@ +import { i18nT } from '../../../../web/i18n/utils'; + +export enum DatasetDataIndexTypeEnum { + default = 'default', + custom = 'custom', + summary = 'summary', + question = 'question', + image = 'image' +} + +export const DatasetDataIndexMap: Record< + `${DatasetDataIndexTypeEnum}`, + { + label: any; + color: string; + } +> = { + [DatasetDataIndexTypeEnum.default]: { + label: i18nT('dataset:data_index_default'), + color: 'gray' + }, + [DatasetDataIndexTypeEnum.custom]: { + label: i18nT('dataset:data_index_custom'), + color: 'blue' + }, + [DatasetDataIndexTypeEnum.summary]: { + label: i18nT('dataset:data_index_summary'), + color: 'green' + }, + [DatasetDataIndexTypeEnum.question]: { + label: i18nT('dataset:data_index_question'), + color: 'red' + }, + [DatasetDataIndexTypeEnum.image]: { + label: i18nT('dataset:data_index_image'), + color: 'purple' + } +}; +export const defaultDatasetIndexData = DatasetDataIndexMap[DatasetDataIndexTypeEnum.custom]; +export const getDatasetIndexMapData = (type: `${DatasetDataIndexTypeEnum}`) => { + return DatasetDataIndexMap[type] || defaultDatasetIndexData; +}; diff --git a/packages/global/core/dataset/training/type.d.ts b/packages/global/core/dataset/training/type.d.ts new file mode 100644 index 000000000000..1bc15ea22b5b --- /dev/null +++ b/packages/global/core/dataset/training/type.d.ts @@ -0,0 +1,20 @@ +import { PushDatasetDataChunkProps } from '../api'; +import { TrainingModeEnum } from '../constants'; + +export type PushDataToTrainingQueueProps = { + teamId: string; + tmbId: string; + datasetId: string; + collectionId: string; + + mode?: TrainingModeEnum; + data: PushDatasetDataChunkProps[]; + prompt?: string; + + agentModel: string; + vectorModel: string; + vlmModel?: string; + + billId?: string; + session?: ClientSession; +}; diff --git a/packages/global/core/dataset/type.d.ts b/packages/global/core/dataset/type.d.ts index 49aabc6258a8..74741a2a95df 100644 --- a/packages/global/core/dataset/type.d.ts +++ b/packages/global/core/dataset/type.d.ts @@ -2,6 +2,7 @@ import type { LLMModelItemType, EmbeddingModelItemType } from '../../core/ai/mod import { PermissionTypeEnum } from '../../support/permission/constant'; import { PushDatasetDataChunkProps } from './api'; import { + DatasetCollectionDataProcessModeEnum, DatasetCollectionTypeEnum, DatasetStatusEnum, DatasetTypeEnum, @@ -12,6 +13,7 @@ import { DatasetPermission } from '../../support/permission/dataset/controller'; import { Permission } from '../../support/permission/controller'; import { APIFileServer, FeishuServer, YuqueServer } from './apiDataset'; import { SourceMemberType } from 'support/user/type'; +import { DatasetDataIndexTypeEnum } from './data/constants'; export type DatasetSchemaType = { _id: string; @@ -23,11 +25,14 @@ export type DatasetSchemaType = { avatar: string; name: string; - vectorModel: string; - agentModel: string; intro: string; type: `${DatasetTypeEnum}`; status: `${DatasetStatusEnum}`; + + vectorModel: string; + agentModel: string; + vlmModel?: string; + websiteConfig?: { url: string; selector: string; @@ -52,26 +57,22 @@ export type DatasetCollectionSchemaType = { parentId?: string; name: string; type: DatasetCollectionTypeEnum; + tags?: string[]; + createTime: Date; updateTime: Date; - forbid?: boolean; - - trainingType: TrainingModeEnum; - chunkSize: number; - chunkSplitter?: string; - qaPrompt?: string; - ocrParse?: boolean; - tags?: string[]; + // Status + forbid?: boolean; + nextSyncTime?: Date; + // Collection metadata fileId?: string; // local file id rawLink?: string; // link url externalFileId?: string; //external file id apiFileId?: string; // api file id externalFileUrl?: string; // external import url - nextSyncTime?: Date; - rawTextLength?: number; hashRawText?: string; metadata?: { @@ -80,6 +81,16 @@ export type DatasetCollectionSchemaType = { [key: string]: any; }; + + // Parse settings + customPdfParse?: boolean; + // Chunk settings + autoIndexes?: boolean; + imageIndex?: boolean; + trainingType: DatasetCollectionDataProcessModeEnum; + chunkSize: number; + chunkSplitter?: string; + qaPrompt?: string; }; export type DatasetCollectionTagsSchemaType = { @@ -90,7 +101,7 @@ export type DatasetCollectionTagsSchemaType = { }; export type DatasetDataIndexItemType = { - defaultIndex: boolean; + type: `${DatasetDataIndexTypeEnum}`; dataId: string; // pg data id text: string; }; @@ -141,6 +152,7 @@ export type DatasetTrainingSchemaType = { chunkIndex: number; weight: number; indexes: Omit[]; + retryCount: number; }; export type CollectionWithDatasetType = DatasetCollectionSchemaType & { @@ -169,9 +181,10 @@ export type DatasetListItemType = { sourceMember?: SourceMemberType; }; -export type DatasetItemType = Omit & { +export type DatasetItemType = Omit & { vectorModel: EmbeddingModelItemType; agentModel: LLMModelItemType; + vlmModel?: LLMModelItemType; permission: DatasetPermission; }; diff --git a/packages/global/core/dataset/utils.ts b/packages/global/core/dataset/utils.ts index d8e6564ace2a..64c330c849c6 100644 --- a/packages/global/core/dataset/utils.ts +++ b/packages/global/core/dataset/utils.ts @@ -1,6 +1,7 @@ import { TrainingModeEnum, DatasetCollectionTypeEnum } from './constants'; import { getFileIcon } from '../../common/file/icon'; import { strIsLink } from '../../common/string/tools'; +import { DatasetDataIndexTypeEnum } from './data/constants'; export function getCollectionIcon( type: DatasetCollectionTypeEnum = DatasetCollectionTypeEnum.file, @@ -38,14 +39,23 @@ export function getSourceNameIcon({ } /* get dataset data default index */ -export function getDefaultIndex(props?: { q?: string; a?: string; dataId?: string }) { - const { q = '', a, dataId } = props || {}; - const qaStr = `${q}\n${a}`.trim(); - return { - defaultIndex: true, - text: a ? qaStr : q, - dataId - }; +export function getDefaultIndex(props?: { q?: string; a?: string }) { + const { q = '', a } = props || {}; + + return [ + { + text: q, + type: DatasetDataIndexTypeEnum.default + }, + ...(a + ? [ + { + text: a, + type: DatasetDataIndexTypeEnum.default + } + ] + : []) + ]; } export const predictDataLimitLength = (mode: TrainingModeEnum, data: any[]) => { diff --git a/packages/global/core/workflow/runtime/utils.ts b/packages/global/core/workflow/runtime/utils.ts index 10e01ceae4d5..b40bbe684349 100644 --- a/packages/global/core/workflow/runtime/utils.ts +++ b/packages/global/core/workflow/runtime/utils.ts @@ -420,137 +420,3 @@ export function rewriteNodeOutputByHistories( }; }); } - -// Parse tags to think and answer - unstream response -export const parseReasoningContent = (text: string): [string, string] => { - const regex = /([\s\S]*?)<\/think>/; - const match = text.match(regex); - - if (!match) { - return ['', text]; - } - - const thinkContent = match[1].trim(); - - // Add answer (remaining text after think tag) - const answerContent = text.slice(match.index! + match[0].length); - - return [thinkContent, answerContent]; -}; - -// Parse tags to think and answer - stream response -export const parseReasoningStreamContent = () => { - let isInThinkTag: boolean | undefined; - - const startTag = ''; - let startTagBuffer = ''; - - const endTag = ''; - let endTagBuffer = ''; - - /* - parseReasoning - 只控制是否主动解析 ,如果接口已经解析了,仍然会返回 think 内容。 - */ - const parsePart = ( - part: { - choices: { - delta: { - content?: string; - reasoning_content?: string; - }; - }[]; - }, - parseReasoning = false - ): [string, string] => { - const content = part.choices?.[0]?.delta?.content || ''; - - // @ts-ignore - const reasoningContent = part.choices?.[0]?.delta?.reasoning_content || ''; - if (reasoningContent || !parseReasoning) { - isInThinkTag = false; - return [reasoningContent, content]; - } - - if (!content) { - return ['', '']; - } - - // 如果不在 think 标签中,或者有 reasoningContent(接口已解析),则返回 reasoningContent 和 content - if (isInThinkTag === false) { - return ['', content]; - } - - // 检测是否为 think 标签开头的数据 - if (isInThinkTag === undefined) { - // Parse content think and answer - startTagBuffer += content; - // 太少内容时候,暂时不解析 - if (startTagBuffer.length < startTag.length) { - return ['', '']; - } - - if (startTagBuffer.startsWith(startTag)) { - isInThinkTag = true; - return [startTagBuffer.slice(startTag.length), '']; - } - - // 如果未命中 think 标签,则认为不在 think 标签中,返回 buffer 内容作为 content - isInThinkTag = false; - return ['', startTagBuffer]; - } - - // 确认是 think 标签内容,开始返回 think 内容,并实时检测 - /* - 检测 方案。 - 存储所有疑似 的内容,直到检测到完整的 标签或超出 长度。 - content 返回值包含以下几种情况: - abc - 完全未命中尾标签 - abc} {hasCharsLen && } {hasDuration && } + {hasPages && } @@ -126,6 +130,7 @@ const UsageDetail = ({ usage, onClose }: { usage: UsageItemType; onClose: () => {hasOutputToken && } {hasCharsLen && } {hasDuration && } + {hasPages && } ))} diff --git a/projects/app/src/pageComponents/account/usage/UsageTable.tsx b/projects/app/src/pageComponents/account/usage/UsageTable.tsx index ccb4bf0b900a..45ae241407c1 100644 --- a/projects/app/src/pageComponents/account/usage/UsageTable.tsx +++ b/projects/app/src/pageComponents/account/usage/UsageTable.tsx @@ -87,8 +87,8 @@ const UsageTableList = ({ 'common:support.wallet.usage.Audio Speech' ), ['support.wallet.usage.Whisper']: t('common:support.wallet.usage.Whisper'), - ['support.wallet.moduleName.index']: t('common:support.wallet.moduleName.index'), - ['support.wallet.moduleName.qa']: t('common:support.wallet.moduleName.qa'), + ['account_usage:embedding_index']: t('account_usage:embedding_index'), + ['account_usage:qa']: t('account_usage:qa'), ['core.dataset.training.Auto mode']: t('common:core.dataset.training.Auto mode'), ['common:core.module.template.ai_chat']: t('common:core.module.template.ai_chat') }, @@ -122,49 +122,51 @@ const UsageTableList = ({ onConfirm={exportUsage} /> - - -
- 完全命中尾标签 - abcabc - 完全命中尾标签 - abc - 完全命中尾标签 - k>abc - 命中一部分尾标签 - */ - // endTagBuffer 专门用来记录疑似尾标签的内容 - if (endTagBuffer) { - endTagBuffer += content; - if (endTagBuffer.includes(endTag)) { - isInThinkTag = false; - const answer = endTagBuffer.slice(endTag.length); - return ['', answer]; - } else if (endTagBuffer.length >= endTag.length) { - // 缓存内容超出尾标签长度,且仍未命中 ,则认为本次猜测 失败,仍处于 think 阶段。 - const tmp = endTagBuffer; - endTagBuffer = ''; - return [tmp, '']; - } - return ['', '']; - } else if (content.includes(endTag)) { - // 返回内容,完整命中,直接结束 - isInThinkTag = false; - const [think, answer] = content.split(endTag); - return [think, answer]; - } else { - // 无 buffer,且未命中 ,开始疑似 检测。 - for (let i = 1; i < endTag.length; i++) { - const partialEndTag = endTag.slice(0, i); - // 命中一部分尾标签 - if (content.endsWith(partialEndTag)) { - const think = content.slice(0, -partialEndTag.length); - endTagBuffer += partialEndTag; - return [think, '']; - } - } - } - - // 完全未命中尾标签,还是 think 阶段。 - return [content, '']; - }; - - const getStartTagBuffer = () => startTagBuffer; - - return { - parsePart, - getStartTagBuffer - }; -}; diff --git a/packages/global/core/workflow/template/system/aiChat/index.ts b/packages/global/core/workflow/template/system/aiChat/index.ts index 8d6aa6cd0626..db5df3d6dc75 100644 --- a/packages/global/core/workflow/template/system/aiChat/index.ts +++ b/packages/global/core/workflow/template/system/aiChat/index.ts @@ -55,7 +55,7 @@ export const AiChatModule: FlowNodeTemplateType = { showStatus: true, isTool: true, courseUrl: '/docs/guide/workbench/workflow/ai_chat/', - version: '4813', + version: '490', inputs: [ Input_Template_SettingAiModel, // --- settings modal diff --git a/packages/global/core/workflow/template/system/tools.ts b/packages/global/core/workflow/template/system/tools.ts index 8ef75d9f5b6d..672deaffad32 100644 --- a/packages/global/core/workflow/template/system/tools.ts +++ b/packages/global/core/workflow/template/system/tools.ts @@ -58,6 +58,13 @@ export const ToolModule: FlowNodeTemplateType = { valueType: WorkflowIOValueTypeEnum.boolean, value: true }, + { + key: NodeInputKeyEnum.aiChatReasoning, + renderTypeList: [FlowNodeInputTypeEnum.hidden], + label: '', + valueType: WorkflowIOValueTypeEnum.boolean, + value: true + }, { key: NodeInputKeyEnum.aiChatTopP, renderTypeList: [FlowNodeInputTypeEnum.hidden], diff --git a/packages/global/package.json b/packages/global/package.json index b00061a0befd..9a8cdcc66831 100644 --- a/packages/global/package.json +++ b/packages/global/package.json @@ -3,14 +3,14 @@ "version": "1.0.0", "dependencies": { "@apidevtools/swagger-parser": "^10.1.0", - "axios": "^1.5.1", + "axios": "^1.8.2", "cron-parser": "^4.9.0", "dayjs": "^1.11.7", "encoding": "^0.1.13", "js-yaml": "^4.1.0", "jschardet": "3.1.1", "nanoid": "^4.0.1", - "next": "14.2.5", + "next": "14.2.21", "openai": "4.61.0", "openapi-types": "^12.1.3", "json5": "^2.2.3", diff --git a/packages/global/support/user/team/controller.d.ts b/packages/global/support/user/team/controller.d.ts index f8c0ea2ffa44..cf17b7addbbd 100644 --- a/packages/global/support/user/team/controller.d.ts +++ b/packages/global/support/user/team/controller.d.ts @@ -10,7 +10,6 @@ export type AuthTeamRoleProps = { export type CreateTeamProps = { name: string; avatar?: string; - defaultTeam?: boolean; memberName?: string; memberAvatar?: string; notificationAccount?: string; diff --git a/packages/global/support/user/team/type.d.ts b/packages/global/support/user/team/type.d.ts index c087046eaeeb..3be91387036c 100644 --- a/packages/global/support/user/team/type.d.ts +++ b/packages/global/support/user/team/type.d.ts @@ -47,7 +47,6 @@ export type TeamMemberSchema = { role: `${TeamMemberRoleEnum}`; status: `${TeamMemberStatusEnum}`; avatar: string; - defaultTeam: boolean; }; export type TeamMemberWithTeamAndUserSchema = TeamMemberSchema & { @@ -65,7 +64,6 @@ export type TeamTmbItemType = { balance?: number; tmbId: string; teamDomain: string; - defaultTeam: boolean; role: `${TeamMemberRoleEnum}`; status: `${TeamMemberStatusEnum}`; notificationAccount?: string; diff --git a/packages/global/support/wallet/usage/constants.ts b/packages/global/support/wallet/usage/constants.ts index b20bc8a6de2a..e2848c7038c6 100644 --- a/packages/global/support/wallet/usage/constants.ts +++ b/packages/global/support/wallet/usage/constants.ts @@ -10,7 +10,8 @@ export enum UsageSourceEnum { wecom = 'wecom', feishu = 'feishu', dingtalk = 'dingtalk', - official_account = 'official_account' + official_account = 'official_account', + pdfParse = 'pdfParse' } export const UsageSourceMap = { @@ -43,5 +44,8 @@ export const UsageSourceMap = { }, [UsageSourceEnum.dingtalk]: { label: i18nT('account_usage:dingtalk') + }, + [UsageSourceEnum.pdfParse]: { + label: i18nT('account_usage:pdf_parse') } }; diff --git a/packages/global/support/wallet/usage/type.d.ts b/packages/global/support/wallet/usage/type.d.ts index f34feb580abc..268279c852e0 100644 --- a/packages/global/support/wallet/usage/type.d.ts +++ b/packages/global/support/wallet/usage/type.d.ts @@ -7,6 +7,7 @@ export type UsageListItemCountType = { outputTokens?: number; charsLength?: number; duration?: number; + pages?: number; // deprecated tokens?: number; diff --git a/packages/plugins/package.json b/packages/plugins/package.json index db95648871dc..e6d65a3b1ad9 100644 --- a/packages/plugins/package.json +++ b/packages/plugins/package.json @@ -5,7 +5,7 @@ "dependencies": { "cheerio": "1.0.0-rc.12", "@types/pg": "^8.6.6", - "axios": "^1.5.1", + "axios": "^1.8.2", "duck-duck-scrape": "^2.2.5", "echarts": "5.4.1", "expr-eval": "^2.0.2", diff --git a/packages/plugins/src/fetchUrl/template.json b/packages/plugins/src/fetchUrl/template.json index 38700ad6a9e2..571787c173e1 100644 --- a/packages/plugins/src/fetchUrl/template.json +++ b/packages/plugins/src/fetchUrl/template.json @@ -16,7 +16,7 @@ "nodeId": "lmpb9v2lo2lk", "name": "插件开始", "intro": "自定义配置外部输入,使用插件时,仅暴露自定义配置的输入", - "avatar": "/imgs/workflow/input.png", + "avatar": "core/workflow/template/workflowStart", "flowNodeType": "pluginInput", "showStatus": false, "position": { @@ -26,14 +26,16 @@ "version": "481", "inputs": [ { - "renderTypeList": ["reference"], + "renderTypeList": ["input", "reference"], "selectedTypeIndex": 0, "valueType": "string", "key": "url", "label": "url", "description": "需要读取的网页链接", "required": true, - "toolDescription": "需要读取的网页链接" + "toolDescription": "需要读取的网页链接", + "list": [], + "defaultValue": "" } ], "outputs": [ @@ -50,12 +52,12 @@ "nodeId": "i7uow4wj2wdp", "name": "插件输出", "intro": "自定义配置外部输出,使用插件时,仅暴露自定义配置的输出", - "avatar": "/imgs/workflow/output.png", + "avatar": "core/workflow/template/pluginOutput", "flowNodeType": "pluginOutput", "showStatus": false, "position": { - "x": 1607.7142331269129, - "y": -150.8808596935447 + "x": 1853.935047606551, + "y": -154.13661665265613 }, "version": "481", "inputs": [ @@ -81,12 +83,12 @@ "nodeId": "ebLCxU43hHuZ", "name": "HTTP 请求", "intro": "可以发出一个 HTTP 请求,实现更为复杂的操作(联网搜索、数据库查询等)", - "avatar": "/imgs/workflow/http.png", + "avatar": "core/workflow/template/httpRequest", "flowNodeType": "httpRequest468", "showStatus": true, "position": { - "x": 1050.9890727421412, - "y": -415.2085119990912 + "x": 1054.2940501177068, + "y": -503.13661665265613 }, "version": "481", "inputs": [ @@ -96,7 +98,7 @@ "valueType": "dynamic", "label": "", "required": false, - "description": "core.module.input.description.HTTP Dynamic Input", + "description": "common:core.module.input.description.HTTP Dynamic Input", "customInputConfig": { "selectValueTypeList": [ "string", @@ -107,16 +109,19 @@ "arrayNumber", "arrayBoolean", "arrayObject", + "arrayAny", "any", "chatHistory", "datasetQuote", "dynamic", - "selectApp", - "selectDataset" + "selectDataset", + "selectApp" ], "showDescription": false, "showDefaultValue": true - } + }, + "debugLabel": "", + "toolDescription": "" }, { "key": "system_httpMethod", @@ -124,17 +129,33 @@ "valueType": "string", "label": "", "value": "POST", - "required": true + "required": true, + "debugLabel": "", + "toolDescription": "" + }, + { + "key": "system_httpTimeout", + "renderTypeList": ["custom"], + "valueType": "number", + "label": "", + "value": 30, + "min": 5, + "max": 600, + "required": true, + "debugLabel": "", + "toolDescription": "" }, { "key": "system_httpReqUrl", "renderTypeList": ["hidden"], "valueType": "string", "label": "", - "description": "core.module.input.description.Http Request Url", + "description": "common:core.module.input.description.Http Request Url", "placeholder": "https://api.ai.com/getInventory", "required": false, - "value": "fetchUrl" + "value": "fetchUrl", + "debugLabel": "", + "toolDescription": "" }, { "key": "system_httpHeader", @@ -142,9 +163,11 @@ "valueType": "any", "value": [], "label": "", - "description": "core.module.input.description.Http Request Header", - "placeholder": "core.module.input.description.Http Request Header", - "required": false + "description": "common:core.module.input.description.Http Request Header", + "placeholder": "common:core.module.input.description.Http Request Header", + "required": false, + "debugLabel": "", + "toolDescription": "" }, { "key": "system_httpParams", @@ -152,7 +175,9 @@ "valueType": "any", "value": [], "label": "", - "required": false + "required": false, + "debugLabel": "", + "toolDescription": "" }, { "key": "system_httpJsonBody", @@ -160,7 +185,29 @@ "valueType": "any", "value": "{\n \"url\": \"{{url}}\"\n}", "label": "", - "required": false + "required": false, + "debugLabel": "", + "toolDescription": "" + }, + { + "key": "system_httpFormBody", + "renderTypeList": ["hidden"], + "valueType": "any", + "value": [], + "label": "", + "required": false, + "debugLabel": "", + "toolDescription": "" + }, + { + "key": "system_httpContentType", + "renderTypeList": ["hidden"], + "valueType": "string", + "value": "json", + "label": "", + "required": false, + "debugLabel": "", + "toolDescription": "" }, { "renderTypeList": ["reference"], @@ -178,12 +225,13 @@ "arrayNumber", "arrayBoolean", "arrayObject", + "arrayAny", "any", "chatHistory", "datasetQuote", "dynamic", - "selectApp", - "selectDataset" + "selectDataset", + "selectApp" ], "showDescription": false, "showDefaultValue": true @@ -193,6 +241,23 @@ } ], "outputs": [ + { + "id": "error", + "key": "error", + "label": "workflow:request_error", + "description": "HTTP请求错误信息,成功时返回空", + "valueType": "object", + "type": "static" + }, + { + "id": "httpRawResponse", + "key": "httpRawResponse", + "required": true, + "label": "workflow:raw_response", + "description": "HTTP请求的原始响应。只能接受字符串或JSON类型响应数据。", + "valueType": "any", + "type": "static" + }, { "id": "system_addOutputParam", "key": "system_addOutputParam", @@ -220,23 +285,6 @@ "showDefaultValue": true } }, - { - "id": "error", - "key": "error", - "label": "请求错误", - "description": "HTTP请求错误信息,成功时返回空", - "valueType": "object", - "type": "static" - }, - { - "id": "httpRawResponse", - "key": "httpRawResponse", - "label": "原始响应", - "required": true, - "description": "HTTP请求的原始响应。只能接受字符串或JSON类型响应数据。", - "valueType": "any", - "type": "static" - }, { "id": "rH4tMV02robs", "valueType": "string", @@ -260,6 +308,34 @@ "sourceHandle": "ebLCxU43hHuZ-source-right", "targetHandle": "i7uow4wj2wdp-target-left" } - ] + ], + "chatConfig": { + "welcomeText": "", + "variables": [], + "questionGuide": { + "open": false, + "model": "gpt-4o-mini", + "customPrompt": "You are an AI assistant tasked with predicting the user's next question based on the conversation history. Your goal is to generate 3 potential questions that will guide the user to continue the conversation. When generating these questions, adhere to the following rules:\n\n1. Use the same language as the user's last question in the conversation history.\n2. Keep each question under 20 characters in length.\n\nAnalyze the conversation history provided to you and use it as context to generate relevant and engaging follow-up questions. Your predictions should be logical extensions of the current topic or related areas that the user might be interested in exploring further.\n\nRemember to maintain consistency in tone and style with the existing conversation while providing diverse options for the user to choose from. Your goal is to keep the conversation flowing naturally and help the user delve deeper into the subject matter or explore related topics." + }, + "ttsConfig": { + "type": "web" + }, + "whisperConfig": { + "open": false, + "autoSend": false, + "autoTTSResponse": false + }, + "chatInputGuide": { + "open": false, + "textList": [], + "customUrl": "" + }, + "instruction": "", + "autoExecute": { + "open": false, + "defaultPrompt": "" + }, + "_id": "677b59849d672185a5671b45" + } } } diff --git a/packages/service/common/file/gridfs/controller.ts b/packages/service/common/file/gridfs/controller.ts index af304714c6c2..f809cc772cb4 100644 --- a/packages/service/common/file/gridfs/controller.ts +++ b/packages/service/common/file/gridfs/controller.ts @@ -18,10 +18,10 @@ export function getGFSCollection(bucket: `${BucketNameEnum}`) { MongoDatasetFileSchema; MongoChatFileSchema; - return connectionMongo.connection.db.collection(`${bucket}.files`); + return connectionMongo.connection.db!.collection(`${bucket}.files`); } export function getGridBucket(bucket: `${BucketNameEnum}`) { - return new connectionMongo.mongo.GridFSBucket(connectionMongo.connection.db, { + return new connectionMongo.mongo.GridFSBucket(connectionMongo.connection.db!, { bucketName: bucket, // @ts-ignore readPreference: ReadPreference.SECONDARY_PREFERRED // Read from secondary node @@ -52,7 +52,9 @@ export async function uploadFile({ const stats = await fsp.stat(path); if (!stats.isFile()) return Promise.reject(`${path} is not a file`); - const readStream = fs.createReadStream(path); + const readStream = fs.createReadStream(path, { + highWaterMark: 256 * 1024 + }); // Add default metadata metadata.teamId = teamId; @@ -62,9 +64,27 @@ export async function uploadFile({ // create a gridfs bucket const bucket = getGridBucket(bucketName); + const fileSize = stats.size; + const chunkSizeBytes = (() => { + // 计算理想块大小:文件大小 ÷ 目标块数(10) + const idealChunkSize = Math.ceil(fileSize / 10); + + // 确保块大小至少为512KB + const minChunkSize = 512 * 1024; // 512KB + + // 取理想块大小和最小块大小中的较大值 + let chunkSize = Math.max(idealChunkSize, minChunkSize); + + // 将块大小向上取整到最接近的64KB的倍数,使其更整齐 + chunkSize = Math.ceil(chunkSize / (64 * 1024)) * (64 * 1024); + + return chunkSize; + })(); + const stream = bucket.openUploadStream(filename, { metadata, - contentType + contentType, + chunkSizeBytes }); // save to gridfs @@ -186,20 +206,25 @@ export async function getDownloadStream({ export const readFileContentFromMongo = async ({ teamId, + tmbId, bucketName, fileId, - isQAImport = false + isQAImport = false, + customPdfParse = false }: { teamId: string; + tmbId: string; bucketName: `${BucketNameEnum}`; fileId: string; isQAImport?: boolean; + customPdfParse?: boolean; }): Promise<{ rawText: string; filename: string; }> => { + const bufferId = `${fileId}-${customPdfParse}`; // read buffer - const fileBuffer = await MongoRawTextBuffer.findOne({ sourceId: fileId }, undefined, { + const fileBuffer = await MongoRawTextBuffer.findOne({ sourceId: bufferId }, undefined, { ...readFromSecondary }).lean(); if (fileBuffer) { @@ -227,9 +252,11 @@ export const readFileContentFromMongo = async ({ // Get raw text const { rawText } = await readRawContentByFileBuffer({ + customPdfParse, extension, isQAImport, teamId, + tmbId, buffer: fileBuffers, encoding, metadata: { @@ -240,7 +267,7 @@ export const readFileContentFromMongo = async ({ // < 14M if (fileBuffers.length < 14 * 1024 * 1024 && rawText.trim()) { MongoRawTextBuffer.create({ - sourceId: fileId, + sourceId: bufferId, rawText, metadata: { filename: file.filename diff --git a/packages/service/common/file/gridfs/utils.ts b/packages/service/common/file/gridfs/utils.ts index 8f27a07187cc..9e376f28f97b 100644 --- a/packages/service/common/file/gridfs/utils.ts +++ b/packages/service/common/file/gridfs/utils.ts @@ -3,13 +3,14 @@ import { PassThrough } from 'stream'; export const gridFsStream2Buffer = (stream: NodeJS.ReadableStream) => { return new Promise((resolve, reject) => { - let tmpBuffer: Buffer = Buffer.from([]); + const chunks: Uint8Array[] = []; stream.on('data', (chunk) => { - tmpBuffer = Buffer.concat([tmpBuffer, chunk]); + chunks.push(chunk); }); stream.on('end', () => { - resolve(tmpBuffer); + const resultBuffer = Buffer.concat(chunks); // 一次性拼接 + resolve(resultBuffer); }); stream.on('error', (err) => { reject(err); @@ -18,25 +19,26 @@ export const gridFsStream2Buffer = (stream: NodeJS.ReadableStream) => { }; export const stream2Encoding = async (stream: NodeJS.ReadableStream) => { - const start = Date.now(); const copyStream = stream.pipe(new PassThrough()); /* get encoding */ const buffer = await (() => { return new Promise((resolve, reject) => { - let tmpBuffer: Buffer = Buffer.from([]); + const chunks: Uint8Array[] = []; + let totalLength = 0; stream.on('data', (chunk) => { - if (tmpBuffer.length < 200) { - tmpBuffer = Buffer.concat([tmpBuffer, chunk]); + if (totalLength < 200) { + chunks.push(chunk); + totalLength += chunk.length; - if (tmpBuffer.length >= 200) { - resolve(tmpBuffer); + if (totalLength >= 200) { + resolve(Buffer.concat(chunks)); } } }); stream.on('end', () => { - resolve(tmpBuffer); + resolve(Buffer.concat(chunks)); }); stream.on('error', (err) => { reject(err); diff --git a/packages/service/common/file/image/controller.ts b/packages/service/common/file/image/controller.ts index f368e45e03c0..0bf8983370ef 100644 --- a/packages/service/common/file/image/controller.ts +++ b/packages/service/common/file/image/controller.ts @@ -6,6 +6,7 @@ import { guessBase64ImageType } from '../utils'; import { readFromSecondary } from '../../mongo/utils'; import { addHours } from 'date-fns'; import { imageFileType } from '@fastgpt/global/common/file/constants'; +import { retryFn } from '@fastgpt/global/common/system/utils'; export const maxImgSize = 1024 * 1024 * 12; const base64MimeRegex = /data:image\/([^\)]+);base64/; @@ -40,13 +41,15 @@ export async function uploadMongoImg({ return Promise.reject(`Invalid image file type: ${mime}`); } - const { _id } = await MongoImage.create({ - teamId, - binary, - metadata: Object.assign({ mime }, metadata), - shareId, - expiredTime: forever ? undefined : addHours(new Date(), 1) - }); + const { _id } = await retryFn(() => + MongoImage.create({ + teamId, + binary, + metadata: Object.assign({ mime }, metadata), + shareId, + expiredTime: forever ? undefined : addHours(new Date(), 1) + }) + ); return `${process.env.NEXT_PUBLIC_BASE_URL || ''}${imageBaseUrl}${String(_id)}.${extension}`; } @@ -118,7 +121,7 @@ export async function delImgByRelatedId({ }: { teamId: string; relateIds: string[]; - session: ClientSession; + session?: ClientSession; }) { if (relateIds.length === 0) return; diff --git a/packages/service/common/file/image/utils.ts b/packages/service/common/file/image/utils.ts new file mode 100644 index 000000000000..57820879df81 --- /dev/null +++ b/packages/service/common/file/image/utils.ts @@ -0,0 +1,34 @@ +import axios from 'axios'; +import { addLog } from '../../system/log'; +import { serverRequestBaseUrl } from '../../api/serverRequest'; +import { getFileContentTypeFromHeader, guessBase64ImageType } from '../utils'; +import { retryFn } from '@fastgpt/global/common/system/utils'; + +export const getImageBase64 = async (url: string) => { + addLog.debug(`Load image to base64: ${url}`); + + try { + const response = await retryFn(() => + axios.get(url, { + baseURL: serverRequestBaseUrl, + responseType: 'arraybuffer', + proxy: false + }) + ); + + const base64 = Buffer.from(response.data, 'binary').toString('base64'); + const imageType = + getFileContentTypeFromHeader(response.headers['content-type']) || + guessBase64ImageType(base64); + + return { + completeBase64: `data:${imageType};base64,${base64}`, + base64, + mime: imageType + }; + } catch (error) { + addLog.debug(`Load image to base64 failed: ${url}`); + console.log(error); + return Promise.reject(error); + } +}; diff --git a/packages/service/common/file/read/utils.ts b/packages/service/common/file/read/utils.ts index ba6863436f7e..7575b3675704 100644 --- a/packages/service/common/file/read/utils.ts +++ b/packages/service/common/file/read/utils.ts @@ -1,18 +1,24 @@ import { uploadMongoImg } from '../image/controller'; import FormData from 'form-data'; - import { WorkerNameEnum, runWorker } from '../../../worker/utils'; import fs from 'fs'; -import type { ReadFileResponse } from '../../../worker/readFile/type'; +import type { ImageType, ReadFileResponse } from '../../../worker/readFile/type'; import axios from 'axios'; import { addLog } from '../../system/log'; -import { batchRun } from '@fastgpt/global/common/fn/utils'; -import { matchMdImgTextAndUpload } from '@fastgpt/global/common/string/markdown'; +import { batchRun } from '@fastgpt/global/common/system/utils'; +import { htmlTable2Md, matchMdImg } from '@fastgpt/global/common/string/markdown'; +import { createPdfParseUsage } from '../../../support/wallet/usage/controller'; +import { getErrText } from '@fastgpt/global/common/error/utils'; +import { delay } from '@fastgpt/global/common/system/utils'; +import { getNanoid } from '@fastgpt/global/common/string/tools'; +import { getImageBase64 } from '../image/utils'; export type readRawTextByLocalFileParams = { teamId: string; + tmbId: string; path: string; encoding: string; + customPdfParse?: boolean; metadata?: Record; }; export const readRawTextByLocalFile = async (params: readRawTextByLocalFileParams) => { @@ -22,46 +28,51 @@ export const readRawTextByLocalFile = async (params: readRawTextByLocalFileParam const buffer = await fs.promises.readFile(path); - const { rawText } = await readRawContentByFileBuffer({ + return readRawContentByFileBuffer({ extension, isQAImport: false, + customPdfParse: params.customPdfParse, teamId: params.teamId, + tmbId: params.tmbId, encoding: params.encoding, buffer, metadata: params.metadata }); - - return { - rawText - }; }; export const readRawContentByFileBuffer = async ({ - extension, - isQAImport, teamId, + tmbId, + + extension, buffer, encoding, - metadata + metadata, + customPdfParse = false, + isQAImport = false }: { - isQAImport?: boolean; - extension: string; teamId: string; + tmbId: string; + + extension: string; buffer: Buffer; encoding: string; metadata?: Record; -}) => { - // Custom read file service - const customReadfileUrl = process.env.CUSTOM_READ_FILE_URL; - const customReadFileExtension = process.env.CUSTOM_READ_FILE_EXTENSION || ''; - const ocrParse = process.env.CUSTOM_READ_FILE_OCR || 'false'; - const readFileFromCustomService = async (): Promise => { - if ( - !customReadfileUrl || - !customReadFileExtension || - !customReadFileExtension.includes(extension) - ) - return; + + customPdfParse?: boolean; + isQAImport: boolean; +}): Promise => { + const systemParse = () => + runWorker(WorkerNameEnum.readFile, { + extension, + encoding, + buffer, + teamId + }); + const parsePdfFromCustomService = async (): Promise => { + const url = global.systemEnv.customPdfParse?.url; + const token = global.systemEnv.customPdfParse?.key; + if (!url) return systemParse(); const start = Date.now(); addLog.info('Parsing files from an external service'); @@ -70,27 +81,32 @@ export const readRawContentByFileBuffer = async ({ data.append('file', buffer, { filename: `file.${extension}` }); - data.append('extension', extension); - data.append('ocr', ocrParse); const { data: response } = await axios.post<{ - success: boolean; - message: string; - data: { - page: number; - markdown: string; - duration: number; - }; - }>(customReadfileUrl, data, { + pages: number; + markdown: string; + error?: Object | string; + }>(url, data, { timeout: 600000, headers: { - ...data.getHeaders() + ...data.getHeaders(), + Authorization: token ? `Bearer ${token}` : undefined } }); + if (response.error) { + return Promise.reject(response.error); + } + addLog.info(`Custom file parsing is complete, time: ${Date.now() - start}ms`); - const rawText = response.data.markdown; - const { text, imageList } = matchMdImgTextAndUpload(rawText); + const rawText = response.markdown; + const { text, imageList } = matchMdImg(rawText); + + createPdfParseUsage({ + teamId, + tmbId, + pages: response.pages + }); return { rawText: text, @@ -98,15 +114,198 @@ export const readRawContentByFileBuffer = async ({ imageList }; }; + const parsePdfFromDoc2x = async (): Promise => { + const doc2xKey = global.systemEnv.customPdfParse?.doc2xKey; + if (!doc2xKey) return systemParse(); - let { rawText, formatText, imageList } = - (await readFileFromCustomService()) || - (await runWorker(WorkerNameEnum.readFile, { - extension, - encoding, - buffer, - teamId - })); + const parseTextImage = async (text: string) => { + // Extract image links and convert to base64 + const imageList: { id: string; url: string }[] = []; + let processedText = text.replace(/!\[.*?\]\((http[^)]+)\)/g, (match, url) => { + const id = `IMAGE_${getNanoid()}_IMAGE`; + imageList.push({ + id, + url + }); + return `![](${id})`; + }); + + // Get base64 from image url + let resultImageList: ImageType[] = []; + await batchRun( + imageList, + async (item) => { + try { + const { base64, mime } = await getImageBase64(item.url); + resultImageList.push({ + uuid: item.id, + mime, + base64 + }); + } catch (error) { + processedText = processedText.replace(item.id, item.url); + addLog.warn(`Failed to get image from ${item.url}: ${getErrText(error)}`); + } + }, + 5 + ); + + return { + text: processedText, + imageList: resultImageList + }; + }; + + let startTime = Date.now(); + + // 1. Get pre-upload URL first + const { data: preupload_data } = await axios + .post<{ code: string; data: { uid: string; url: string } }>( + 'https://v2.doc2x.noedgeai.com/api/v2/parse/preupload', + null, + { + headers: { + Authorization: `Bearer ${doc2xKey}` + } + } + ) + .catch((error) => { + return Promise.reject( + `[Pre-upload Error] Failed to get pre-upload URL: ${getErrText(error)}` + ); + }); + if (preupload_data?.code !== 'success') { + return Promise.reject(`Failed to get pre-upload URL: ${JSON.stringify(preupload_data)}`); + } + + const upload_url = preupload_data.data.url; + const uid = preupload_data.data.uid; + + // 2. Upload file to pre-signed URL with binary stream + const blob = new Blob([buffer], { type: 'application/pdf' }); + const response = await axios + .put(upload_url, blob, { + headers: { + 'Content-Type': 'application/pdf' + } + }) + .catch((error) => { + return Promise.reject(`[Upload Error] Failed to upload file: ${getErrText(error)}`); + }); + if (response.status !== 200) { + return Promise.reject(`Upload failed with status ${response.status}: ${response.statusText}`); + } + + await delay(5000); + addLog.debug(`Uploaded file to Doc2x, uid: ${uid}`); + // 3. Get the result by uid + const checkResult = async (retry = 30) => { + if (retry <= 0) { + return Promise.reject( + `[Parse Timeout Error] Failed to get result (uid: ${uid}): Process timeout` + ); + } + + try { + const { data: result_data } = await axios + .get<{ + code: string; + data: { + progress: number; + status: 'processing' | 'failed' | 'success'; + result: { + pages: { + md: string; + }[]; + }; + }; + }>(`https://v2.doc2x.noedgeai.com/api/v2/parse/status?uid=${uid}`, { + headers: { + Authorization: `Bearer ${doc2xKey}` + } + }) + .catch((error) => { + return Promise.reject( + `[Parse Status Error] Failed to get parse status: ${getErrText(error)}` + ); + }); + + // Error + if (!['ok', 'success'].includes(result_data.code)) { + return Promise.reject( + `Failed to get result (uid: ${uid}): ${JSON.stringify(result_data)}` + ); + } + + // Process + if (['ready', 'processing'].includes(result_data.data.status)) { + addLog.debug(`Waiting for the result, uid: ${uid}`); + await delay(5000); + return checkResult(retry - 1); + } + + // Finifsh + if (result_data.data.status === 'success') { + const result = result_data.data.result.pages + .map((page) => page.md) + .join('') + // Do some post-processing + .replace(/\\[\(\)]/g, '$') + .replace(/\\[\[\]]/g, '$$') + .replace(/]*)?(?:\s*\/>|>)/g, '![img]($1)') + .replace(//g, '') + .replace(//g, '') + .replace(/\$(.+?)\s+\\tag\{(.+?)\}\$/g, '$$$1 \\qquad \\qquad ($2)$$') + .replace(/\\text\{([^}]*?)(\b\w+)_(\w+\b)([^}]*?)\}/g, '\\text{$1$2\\_$3$4}'); + + const { text, imageList } = await parseTextImage(htmlTable2Md(result)); + + return { + pages: result_data.data.result.pages.length, + text, + imageList + }; + } + return checkResult(retry - 1); + } catch (error) { + if (retry > 1) { + await delay(100); + return checkResult(retry - 1); + } + return Promise.reject(error); + } + }; + + const { pages, text, imageList } = await checkResult(); + + createPdfParseUsage({ + teamId, + tmbId, + pages + }); + + addLog.info(`Doc2x parse success, time: ${Date.now() - startTime}ms`); + return { + rawText: text, + formatText: text, + imageList + }; + }; + // Custom read file service + const pdfParseFn = async (): Promise => { + if (!customPdfParse) return systemParse(); + if (global.systemEnv.customPdfParse?.url) return parsePdfFromCustomService(); + if (global.systemEnv.customPdfParse?.doc2xKey) return parsePdfFromDoc2x(); + + return systemParse(); + }; + + let { rawText, formatText, imageList } = await (async () => { + if (extension === 'pdf') { + return await pdfParseFn(); + } + return await systemParse(); + })(); // markdown data format if (imageList) { @@ -116,14 +315,14 @@ export const readRawContentByFileBuffer = async ({ return await uploadMongoImg({ base64Img: `data:${item.mime};base64,${item.base64}`, teamId, - // expiredTime: addHours(new Date(), 1), metadata: { ...metadata, mime: item.mime } }); } catch (error) { - return ''; + addLog.warn('Upload file image error', { error }); + return 'Upload load image error'; } })(); rawText = rawText.replace(item.uuid, src); @@ -142,5 +341,5 @@ export const readRawContentByFileBuffer = async ({ } } - return { rawText }; + return { rawText, formatText, imageList }; }; diff --git a/packages/service/common/mongo/index.ts b/packages/service/common/mongo/index.ts index 09235bb16a26..02b4213e6b85 100644 --- a/packages/service/common/mongo/index.ts +++ b/packages/service/common/mongo/index.ts @@ -38,10 +38,12 @@ const addCommonMiddleware = (schema: mongoose.Schema) => { schema.post(op, function (this: any, result: any, next) { if (this._startTime) { const duration = Date.now() - this._startTime; - const warnLogData = { - query: this._query, - op, + collectionName: this.collection?.name, + op: this.op, + ...(this._query && { query: this._query }), + ...(this._update && { update: this._update }), + ...(this._delete && { delete: this._delete }), duration }; diff --git a/packages/service/common/mongo/init.ts b/packages/service/common/mongo/init.ts index 57b877e47773..50cb8f463429 100644 --- a/packages/service/common/mongo/init.ts +++ b/packages/service/common/mongo/init.ts @@ -16,16 +16,30 @@ export async function connectMongo(): Promise { console.log('mongo start connect'); try { - connectionMongo.set('strictQuery', true); + // Remove existing listeners to prevent duplicates + connectionMongo.connection.removeAllListeners('error'); + connectionMongo.connection.removeAllListeners('disconnected'); + connectionMongo.set('strictQuery', 'throw'); connectionMongo.connection.on('error', async (error) => { console.log('mongo error', error); - await connectionMongo.disconnect(); - await delay(1000); - connectMongo(); + try { + if (connectionMongo.connection.readyState !== 0) { + await connectionMongo.disconnect(); + await delay(1000); + await connectMongo(); + } + } catch (error) {} }); - connectionMongo.connection.on('disconnected', () => { + connectionMongo.connection.on('disconnected', async () => { console.log('mongo disconnected'); + try { + if (connectionMongo.connection.readyState !== 0) { + await connectionMongo.disconnect(); + await delay(1000); + await connectMongo(); + } + } catch (error) {} }); await connectionMongo.connect(process.env.MONGODB_URI as string, { diff --git a/packages/service/common/string/cheerio.ts b/packages/service/common/string/cheerio.ts index 32726eb04cdf..4e8b52d1061c 100644 --- a/packages/service/common/string/cheerio.ts +++ b/packages/service/common/string/cheerio.ts @@ -2,6 +2,7 @@ import { UrlFetchParams, UrlFetchResponse } from '@fastgpt/global/common/file/ap import * as cheerio from 'cheerio'; import axios from 'axios'; import { htmlToMarkdown } from './utils'; +import { isInternalAddress } from '../system/utils'; export const cheerioToHtml = ({ fetchUrl, @@ -75,6 +76,16 @@ export const urlsFetch = async ({ const response = await Promise.all( urlList.map(async (url) => { + const isInternal = isInternalAddress(url); + if (isInternal) { + return { + url, + title: '', + content: 'Cannot fetch internal url', + selector: '' + }; + } + try { const fetchRes = await axios.get(url, { timeout: 30000 diff --git a/packages/service/common/system/tools.ts b/packages/service/common/system/tools.ts index a52e2a79b93a..321c2fbe0314 100644 --- a/packages/service/common/system/tools.ts +++ b/packages/service/common/system/tools.ts @@ -10,6 +10,11 @@ export const SERVICE_LOCAL_HOST = export const initFastGPTConfig = (config?: FastGPTConfigFileType) => { if (!config) return; + // Special config computed + config.feConfigs.showCustomPdfParse = + !!config.systemEnv.customPdfParse?.url || !!config.systemEnv.customPdfParse?.doc2xKey; + config.feConfigs.customPdfParsePrice = config.systemEnv.customPdfParse?.price || 0; + global.feConfigs = config.feConfigs; global.systemEnv = config.systemEnv; global.subPlans = config.subPlans; diff --git a/packages/service/common/system/utils.ts b/packages/service/common/system/utils.ts new file mode 100644 index 000000000000..13837aab4089 --- /dev/null +++ b/packages/service/common/system/utils.ts @@ -0,0 +1,63 @@ +import { SERVICE_LOCAL_HOST } from './tools'; + +export const isInternalAddress = (url: string): boolean => { + try { + const parsedUrl = new URL(url); + const hostname = parsedUrl.hostname; + const fullUrl = parsedUrl.toString(); + + // Check for localhost and common internal domains + if (hostname === SERVICE_LOCAL_HOST) { + return true; + } + + // Metadata endpoints whitelist + const metadataEndpoints = [ + // AWS + 'http://169.254.169.254/latest/meta-data/', + // Azure + 'http://169.254.169.254/metadata/instance?api-version=2021-02-01', + // GCP + 'http://metadata.google.internal/computeMetadata/v1/', + // Alibaba Cloud + 'http://100.100.100.200/latest/meta-data/', + // Tencent Cloud + 'http://metadata.tencentyun.com/latest/meta-data/', + // Huawei Cloud + 'http://169.254.169.254/latest/meta-data/' + ]; + if (metadataEndpoints.some((endpoint) => fullUrl.startsWith(endpoint))) { + return true; + } + + // For IP addresses, check if they are internal + const ipv4Pattern = /^(\d{1,3}\.){3}\d{1,3}$/; + if (!ipv4Pattern.test(hostname)) { + return false; // Not an IP address, so it's a domain name - consider it external by default + } + + // ... existing IP validation code ... + const parts = hostname.split('.').map(Number); + + if (parts.length !== 4 || parts.some((part) => part < 0 || part > 255)) { + return false; + } + + // Only allow public IP ranges + return ( + parts[0] !== 0 && + parts[0] !== 10 && + parts[0] !== 127 && + !(parts[0] === 169 && parts[1] === 254) && + !(parts[0] === 172 && parts[1] >= 16 && parts[1] <= 31) && + !(parts[0] === 192 && parts[1] === 168) && + !(parts[0] >= 224 && parts[0] <= 239) && + !(parts[0] >= 240 && parts[0] <= 255) && + !(parts[0] === 100 && parts[1] >= 64 && parts[1] <= 127) && + !(parts[0] === 9 && parts[1] === 0) && + !(parts[0] === 11 && parts[1] === 0) + ); + } catch { + return false; // If URL parsing fails, reject it as potentially unsafe + } +}; diff --git a/packages/service/common/vectorStore/pg/class.ts b/packages/service/common/vectorStore/pg/class.ts index ba08adf439f5..6b7f42bd3ff7 100644 --- a/packages/service/common/vectorStore/pg/class.ts +++ b/packages/service/common/vectorStore/pg/class.ts @@ -164,34 +164,22 @@ export class PgVectorCtrl { } try { - // const explan: any = await PgClient.query( - // `BEGIN; - // SET LOCAL hnsw.ef_search = ${global.systemEnv?.pgHNSWEfSearch || 100}; - // EXPLAIN ANALYZE select id, collection_id, vector <#> '[${vector}]' AS score - // from ${DatasetVectorTableName} - // where team_id='${teamId}' - // AND dataset_id IN (${datasetIds.map((id) => `'${String(id)}'`).join(',')}) - // ${forbidCollectionSql} - // order by score limit ${limit}; - // COMMIT;` - // ); - // console.log(explan[2].rows); - const results: any = await PgClient.query( - ` - BEGIN; + `BEGIN; SET LOCAL hnsw.ef_search = ${global.systemEnv?.pgHNSWEfSearch || 100}; - select id, collection_id, vector <#> '[${vector}]' AS score - from ${DatasetVectorTableName} - where team_id='${teamId}' - AND dataset_id IN (${datasetIds.map((id) => `'${String(id)}'`).join(',')}) - ${filterCollectionIdSql} - ${forbidCollectionSql} - order by score limit ${limit}; + SET LOCAL hnsw.iterative_scan = relaxed_order; + WITH relaxed_results AS MATERIALIZED ( + select id, collection_id, vector <#> '[${vector}]' AS score + from ${DatasetVectorTableName} + where team_id='${teamId}' + AND dataset_id IN (${datasetIds.map((id) => `'${String(id)}'`).join(',')}) + ${filterCollectionIdSql} + ${forbidCollectionSql} + order by score limit ${limit} + ) SELECT id, collection_id, score FROM relaxed_results ORDER BY score; COMMIT;` ); - - const rows = results?.[2]?.rows as PgSearchRawType[]; + const rows = results?.[3]?.rows as PgSearchRawType[]; return { results: rows.map((item) => ({ diff --git a/packages/service/core/ai/audio/speech.ts b/packages/service/core/ai/audio/speech.ts index 3d82f68c6179..0bacaae4e564 100644 --- a/packages/service/core/ai/audio/speech.ts +++ b/packages/service/core/ai/audio/speech.ts @@ -43,13 +43,13 @@ export async function text2Speech({ const readableStream = response.body as unknown as NodeJS.ReadableStream; readableStream.pipe(res); - let bufferStore = Buffer.from([]); + const chunks: Uint8Array[] = []; readableStream.on('data', (chunk) => { - bufferStore = Buffer.concat([bufferStore, chunk]); + chunks.push(chunk); }); readableStream.on('end', () => { - onSuccess({ model, buffer: bufferStore }); + onSuccess({ model, buffer: Buffer.concat(chunks) }); }); readableStream.on('error', (e) => { onError(e); diff --git a/packages/service/core/ai/config.ts b/packages/service/core/ai/config.ts index 3fa62eec9c13..15013b07f054 100644 --- a/packages/service/core/ai/config.ts +++ b/packages/service/core/ai/config.ts @@ -11,14 +11,17 @@ import { i18nT } from '../../../web/i18n/utils'; import { OpenaiAccountType } from '@fastgpt/global/support/user/team/type'; import { getLLMModel } from './model'; -export const openaiBaseUrl = process.env.OPENAI_BASE_URL || 'https://api.openai.com/v1'; +const aiProxyBaseUrl = process.env.AIPROXY_API_ENDPOINT + ? `${process.env.AIPROXY_API_ENDPOINT}/v1` + : undefined; +const openaiBaseUrl = aiProxyBaseUrl || process.env.OPENAI_BASE_URL || 'https://api.openai.com/v1'; +const openaiBaseKey = process.env.AIPROXY_API_TOKEN || process.env.CHAT_API_KEY || ''; export const getAIApi = (props?: { userKey?: OpenaiAccountType; timeout?: number }) => { const { userKey, timeout } = props || {}; const baseUrl = userKey?.baseUrl || global?.systemEnv?.oneapiUrl || openaiBaseUrl; - const apiKey = userKey?.key || global?.systemEnv?.chatApiKey || process.env.CHAT_API_KEY || ''; - + const apiKey = userKey?.key || global?.systemEnv?.chatApiKey || openaiBaseKey; return new OpenAI({ baseURL: baseUrl, apiKey, @@ -32,7 +35,7 @@ export const getAxiosConfig = (props?: { userKey?: OpenaiAccountType }) => { const { userKey } = props || {}; const baseUrl = userKey?.baseUrl || global?.systemEnv?.oneapiUrl || openaiBaseUrl; - const apiKey = userKey?.key || global?.systemEnv?.chatApiKey || process.env.CHAT_API_KEY || ''; + const apiKey = userKey?.key || global?.systemEnv?.chatApiKey || openaiBaseKey; return { baseUrl, @@ -72,6 +75,7 @@ export const createChatCompletion = async ({ userKey, timeout: formatTimeout }); + const response = await ai.chat.completions.create(body, { ...options, ...(modelConstantsData.requestUrl ? { path: modelConstantsData.requestUrl } : {}), diff --git a/packages/service/core/ai/config/provider/AliCloud.json b/packages/service/core/ai/config/provider/AliCloud.json index 703ed71d03d0..64a175c56ef2 100644 --- a/packages/service/core/ai/config/provider/AliCloud.json +++ b/packages/service/core/ai/config/provider/AliCloud.json @@ -1,4 +1,10 @@ { "provider": "AliCloud", - "list": [] -} \ No newline at end of file + "list": [ + { + "model": "SenseVoiceSmall", + "name": "SenseVoiceSmall", + "type": "stt" + } + ] +} diff --git a/packages/service/core/ai/config/provider/DeepSeek.json b/packages/service/core/ai/config/provider/DeepSeek.json index df9369e383f2..91b748e85831 100644 --- a/packages/service/core/ai/config/provider/DeepSeek.json +++ b/packages/service/core/ai/config/provider/DeepSeek.json @@ -46,8 +46,8 @@ "defaultConfig": {}, "fieldMap": {}, "type": "llm", - "showTopP": true, - "showStopSign": true + "showTopP": false, + "showStopSign": false } ] } diff --git a/packages/service/core/ai/config/provider/Moonshot.json b/packages/service/core/ai/config/provider/Moonshot.json index 796c529b8b73..f33b09ebea4f 100644 --- a/packages/service/core/ai/config/provider/Moonshot.json +++ b/packages/service/core/ai/config/provider/Moonshot.json @@ -75,6 +75,81 @@ "showTopP": true, "showStopSign": true, "responseFormatList": ["text", "json_object"] + }, + { + "model": "moonshot-v1-8k-vision-preview", + "name": "moonshot-v1-8k-vision-preview", + "maxContext": 8000, + "maxResponse": 4000, + "quoteMaxToken": 6000, + "maxTemperature": 1, + "vision": true, + "toolChoice": true, + "functionCall": false, + "defaultSystemChatPrompt": "", + "datasetProcess": true, + "usedInClassify": true, + "customCQPrompt": "", + "usedInExtractFields": true, + "usedInQueryExtension": true, + "customExtractPrompt": "", + "usedInToolCall": true, + "defaultConfig": {}, + "fieldMap": {}, + "type": "llm", + "showTopP": true, + "showStopSign": true, + "responseFormatList": ["text", "json_object"] + }, + { + "model": "moonshot-v1-32k-vision-preview", + "name": "moonshot-v1-32k-vision-preview", + "maxContext": 32000, + "maxResponse": 4000, + "quoteMaxToken": 32000, + "maxTemperature": 1, + "vision": true, + "toolChoice": true, + "functionCall": false, + "defaultSystemChatPrompt": "", + "datasetProcess": true, + "usedInClassify": true, + "customCQPrompt": "", + "usedInExtractFields": true, + "usedInQueryExtension": true, + "customExtractPrompt": "", + "usedInToolCall": true, + "defaultConfig": {}, + "fieldMap": {}, + "type": "llm", + "showTopP": true, + "showStopSign": true, + "responseFormatList": ["text", "json_object"] + }, + { + "model": "moonshot-v1-128k-vision-preview", + "name": "moonshot-v1-128k-vision-preview", + "maxContext": 128000, + "maxResponse": 4000, + "quoteMaxToken": 60000, + "maxTemperature": 1, + "vision": true, + "toolChoice": true, + "functionCall": false, + "defaultSystemChatPrompt": "", + "datasetProcess": true, + "usedInClassify": true, + "customCQPrompt": "", + "usedInExtractFields": true, + "usedInQueryExtension": true, + "customExtractPrompt": "", + "usedInToolCall": true, + "defaultConfig": {}, + "fieldMap": {}, + "type": "llm", + "showTopP": true, + "showStopSign": true, + "responseFormatList": ["text", "json_object"] } ] } diff --git a/packages/service/core/ai/config/provider/Qwen.json b/packages/service/core/ai/config/provider/Qwen.json index f9cef18e580e..96ac4a3ab477 100644 --- a/packages/service/core/ai/config/provider/Qwen.json +++ b/packages/service/core/ai/config/provider/Qwen.json @@ -122,6 +122,56 @@ "showTopP": true, "showStopSign": true }, + { + "model": "qwq-plus", + "name": "qwq-plus", + "maxContext": 128000, + "maxResponse": 8000, + "quoteMaxToken": 100000, + "maxTemperature": null, + "vision": false, + "reasoning": true, + "toolChoice": true, + "functionCall": false, + "defaultSystemChatPrompt": "", + "datasetProcess": false, + "usedInClassify": false, + "customCQPrompt": "", + "usedInExtractFields": false, + "usedInQueryExtension": false, + "customExtractPrompt": "", + "usedInToolCall": true, + "defaultConfig": {}, + "fieldMap": {}, + "type": "llm", + "showTopP": false, + "showStopSign": false + }, + { + "model": "qwq-32b", + "name": "qwq-32b", + "maxContext": 128000, + "maxResponse": 8000, + "quoteMaxToken": 100000, + "maxTemperature": null, + "vision": false, + "reasoning": true, + "toolChoice": true, + "functionCall": false, + "defaultSystemChatPrompt": "", + "datasetProcess": false, + "usedInClassify": false, + "customCQPrompt": "", + "usedInExtractFields": false, + "usedInQueryExtension": false, + "customExtractPrompt": "", + "usedInToolCall": true, + "defaultConfig": {}, + "fieldMap": {}, + "type": "llm", + "showTopP": false, + "showStopSign": false + }, { "model": "qwen-coder-turbo", "name": "qwen-coder-turbo", diff --git a/packages/service/core/ai/config/utils.ts b/packages/service/core/ai/config/utils.ts index c64ea2c57bc7..86c3786df1c4 100644 --- a/packages/service/core/ai/config/utils.ts +++ b/packages/service/core/ai/config/utils.ts @@ -163,6 +163,13 @@ export const loadSystemModels = async (init = false) => { global.systemDefaultModel.rerank = Array.from(global.reRankModelMap.values())[0]; } + // Sort model list + global.systemActiveModelList.sort((a, b) => { + const providerA = getModelProvider(a.provider); + const providerB = getModelProvider(b.provider); + return providerA.order - providerB.order; + }); + console.log('Load models success', JSON.stringify(global.systemActiveModelList, null, 2)); } catch (error) { console.error('Load models error', error); diff --git a/packages/service/core/ai/model.ts b/packages/service/core/ai/model.ts index 185881a23f67..3b0f0aef8ed7 100644 --- a/packages/service/core/ai/model.ts +++ b/packages/service/core/ai/model.ts @@ -13,6 +13,11 @@ export const getDatasetModel = (model?: string) => { ?.find((item) => item.model === model || item.name === model) ?? getDefaultLLMModel() ); }; +export const getVlmModel = (model?: string) => { + return Array.from(global.llmModelMap.values()) + ?.filter((item) => item.vision) + ?.find((item) => item.model === model || item.name === model); +}; export const getDefaultEmbeddingModel = () => global?.systemDefaultModel.embedding!; export const getEmbeddingModel = (model?: string) => { diff --git a/projects/app/src/pages/api/v1/chat/utils.test.ts b/packages/service/core/ai/utils.test.ts similarity index 96% rename from projects/app/src/pages/api/v1/chat/utils.test.ts rename to packages/service/core/ai/utils.test.ts index 2f766abadc2b..77e2f9166e48 100644 --- a/projects/app/src/pages/api/v1/chat/utils.test.ts +++ b/packages/service/core/ai/utils.test.ts @@ -1,5 +1,5 @@ -import '@/pages/api/__mocks__/base'; -import { parseReasoningStreamContent } from '@fastgpt/global/core/workflow/runtime/utils'; +import { parseReasoningStreamContent } from './utils'; +import { expect, test } from 'vitest'; test('Parse reasoning stream content test', async () => { const partList = [ diff --git a/packages/service/core/ai/utils.ts b/packages/service/core/ai/utils.ts index 7627b2c8753a..a35e164c9471 100644 --- a/packages/service/core/ai/utils.ts +++ b/packages/service/core/ai/utils.ts @@ -95,11 +95,145 @@ export const llmCompletionsBodyFormat = ( return requestBody as unknown as InferCompletionsBody; }; -export const llmStreamResponseToText = async (response: StreamChatType) => { +export const llmStreamResponseToAnswerText = async (response: StreamChatType) => { let answer = ''; for await (const part of response) { const content = part.choices?.[0]?.delta?.content || ''; answer += content; } - return answer; + return parseReasoningContent(answer)[1]; +}; + +// Parse tags to think and answer - unstream response +export const parseReasoningContent = (text: string): [string, string] => { + const regex = /([\s\S]*?)<\/think>/; + const match = text.match(regex); + + if (!match) { + return ['', text]; + } + + const thinkContent = match[1].trim(); + + // Add answer (remaining text after think tag) + const answerContent = text.slice(match.index! + match[0].length); + + return [thinkContent, answerContent]; +}; + +// Parse tags to think and answer - stream response +export const parseReasoningStreamContent = () => { + let isInThinkTag: boolean | undefined; + + const startTag = ''; + let startTagBuffer = ''; + + const endTag = ''; + let endTagBuffer = ''; + + /* + parseReasoning - 只控制是否主动解析 ,如果接口已经解析了,仍然会返回 think 内容。 + */ + const parsePart = ( + part: { + choices: { + delta: { + content?: string; + reasoning_content?: string; + }; + }[]; + }, + parseReasoning = false + ): [string, string] => { + const content = part.choices?.[0]?.delta?.content || ''; + + // @ts-ignore + const reasoningContent = part.choices?.[0]?.delta?.reasoning_content || ''; + if (reasoningContent || !parseReasoning) { + isInThinkTag = false; + return [reasoningContent, content]; + } + + if (!content) { + return ['', '']; + } + + // 如果不在 think 标签中,或者有 reasoningContent(接口已解析),则返回 reasoningContent 和 content + if (isInThinkTag === false) { + return ['', content]; + } + + // 检测是否为 think 标签开头的数据 + if (isInThinkTag === undefined) { + // Parse content think and answer + startTagBuffer += content; + // 太少内容时候,暂时不解析 + if (startTagBuffer.length < startTag.length) { + return ['', '']; + } + + if (startTagBuffer.startsWith(startTag)) { + isInThinkTag = true; + return [startTagBuffer.slice(startTag.length), '']; + } + + // 如果未命中 think 标签,则认为不在 think 标签中,返回 buffer 内容作为 content + isInThinkTag = false; + return ['', startTagBuffer]; + } + + // 确认是 think 标签内容,开始返回 think 内容,并实时检测 + /* + 检测 方案。 + 存储所有疑似 的内容,直到检测到完整的 标签或超出 长度。 + content 返回值包含以下几种情况: + abc - 完全未命中尾标签 + abc - 完全命中尾标签 + abcabc - 完全命中尾标签 + abc - 完全命中尾标签 + k>abc - 命中一部分尾标签 + */ + // endTagBuffer 专门用来记录疑似尾标签的内容 + if (endTagBuffer) { + endTagBuffer += content; + if (endTagBuffer.includes(endTag)) { + isInThinkTag = false; + const answer = endTagBuffer.slice(endTag.length); + return ['', answer]; + } else if (endTagBuffer.length >= endTag.length) { + // 缓存内容超出尾标签长度,且仍未命中 ,则认为本次猜测 失败,仍处于 think 阶段。 + const tmp = endTagBuffer; + endTagBuffer = ''; + return [tmp, '']; + } + return ['', '']; + } else if (content.includes(endTag)) { + // 返回内容,完整命中,直接结束 + isInThinkTag = false; + const [think, answer] = content.split(endTag); + return [think, answer]; + } else { + // 无 buffer,且未命中 ,开始疑似 检测。 + for (let i = 1; i < endTag.length; i++) { + const partialEndTag = endTag.slice(0, i); + // 命中一部分尾标签 + if (content.endsWith(partialEndTag)) { + const think = content.slice(0, -partialEndTag.length); + endTagBuffer += partialEndTag; + return [think, '']; + } + } + } + + // 完全未命中尾标签,还是 think 阶段。 + return [content, '']; + }; + + const getStartTagBuffer = () => startTagBuffer; + + return { + parsePart, + getStartTagBuffer + }; }; diff --git a/packages/service/core/app/plugin/type.d.ts b/packages/service/core/app/plugin/type.d.ts index 1275ec6d0ce5..c9e760bd8065 100644 --- a/packages/service/core/app/plugin/type.d.ts +++ b/packages/service/core/app/plugin/type.d.ts @@ -25,6 +25,7 @@ export type SystemPluginConfigSchemaType = { templateType: string; associatedPluginId: string; userGuide: string; + author?: string; }; }; diff --git a/packages/service/core/app/templates/templateSchema.ts b/packages/service/core/app/templates/templateSchema.ts index 7e61a1b86c7a..82643741222a 100644 --- a/packages/service/core/app/templates/templateSchema.ts +++ b/packages/service/core/app/templates/templateSchema.ts @@ -9,38 +9,23 @@ const AppTemplateSchema = new Schema({ type: String, required: true }, - name: { - type: String - }, - intro: { - type: String - }, - avatar: { - type: String - }, + name: String, + intro: String, + avatar: String, + author: String, tags: { type: [String], default: undefined }, - type: { - type: String - }, - isActive: { - type: Boolean - }, - userGuide: { - type: Object - }, - isQuickTemplate: { - type: Boolean - }, + type: String, + isActive: Boolean, + userGuide: Object, + isQuickTemplate: Boolean, order: { type: Number, default: -1 }, - workflow: { - type: Object - } + workflow: Object }); AppTemplateSchema.index({ templateId: 1 }); diff --git a/packages/service/core/chat/utils.ts b/packages/service/core/chat/utils.ts index 52edb2cbbfaf..b5a70ace235d 100644 --- a/packages/service/core/chat/utils.ts +++ b/packages/service/core/chat/utils.ts @@ -9,10 +9,9 @@ import type { } from '@fastgpt/global/core/ai/type.d'; import axios from 'axios'; import { ChatCompletionRequestMessageRoleEnum } from '@fastgpt/global/core/ai/constants'; -import { getFileContentTypeFromHeader, guessBase64ImageType } from '../../common/file/utils'; -import { serverRequestBaseUrl } from '../../common/api/serverRequest'; import { i18nT } from '../../../web/i18n/utils'; import { addLog } from '../../common/system/log'; +import { getImageBase64 } from '../../common/file/image/utils'; export const filterGPTMessageByMaxContext = async ({ messages = [], @@ -166,25 +165,13 @@ export const loadRequestMessages = async ({ try { // If imgUrl is a local path, load image from local, and set url to base64 if (imgUrl.startsWith('/') || process.env.MULTIPLE_DATA_TO_BASE64 === 'true') { - addLog.debug('Load image from local server', { - baseUrl: serverRequestBaseUrl, - requestUrl: imgUrl - }); - const response = await axios.get(imgUrl, { - baseURL: serverRequestBaseUrl, - responseType: 'arraybuffer', - proxy: false - }); - const base64 = Buffer.from(response.data, 'binary').toString('base64'); - const imageType = - getFileContentTypeFromHeader(response.headers['content-type']) || - guessBase64ImageType(base64); + const { completeBase64: base64 } = await getImageBase64(imgUrl); return { ...item, image_url: { ...item.image_url, - url: `data:${imageType};base64,${base64}` + url: base64 } }; } @@ -223,7 +210,8 @@ export const loadRequestMessages = async ({ await Promise.all( content.map(async (item) => { if (item.type === 'text') { - if (item.text) return parseStringWithImages(item.text); + // If it is array, not need to parse image + if (item.text) return item; return; } if (item.type === 'file_url') return; // LLM not support file_url diff --git a/packages/service/core/dataset/apiDataset/api.ts b/packages/service/core/dataset/apiDataset/api.ts index 162d685f59b0..7d2ecb4da4e3 100644 --- a/packages/service/core/dataset/apiDataset/api.ts +++ b/packages/service/core/dataset/apiDataset/api.ts @@ -108,7 +108,15 @@ export const useApiDatasetRequest = ({ apiServer }: { apiServer: APIFileServer } return formattedFiles; }; - const getFileContent = async ({ teamId, apiFileId }: { teamId: string; apiFileId: string }) => { + const getFileContent = async ({ + teamId, + tmbId, + apiFileId + }: { + teamId: string; + tmbId: string; + apiFileId: string; + }) => { const data = await request( `/v1/file/content`, { id: apiFileId }, @@ -123,6 +131,7 @@ export const useApiDatasetRequest = ({ apiServer }: { apiServer: APIFileServer } if (previewUrl) { const rawText = await readFileRawTextByUrl({ teamId, + tmbId, url: previewUrl, relatedId: apiFileId }); diff --git a/packages/service/core/dataset/collection/controller.ts b/packages/service/core/dataset/collection/controller.ts index 4ba57bb35669..0dfcc6152e0d 100644 --- a/packages/service/core/dataset/collection/controller.ts +++ b/packages/service/core/dataset/collection/controller.ts @@ -1,6 +1,6 @@ import { DatasetCollectionTypeEnum, - TrainingModeEnum + DatasetCollectionDataProcessModeEnum } from '@fastgpt/global/core/dataset/constants'; import type { CreateDatasetCollectionParams } from '@fastgpt/global/core/dataset/api.d'; import { MongoDatasetCollection } from './schema'; @@ -19,12 +19,14 @@ import { predictDataLimitLength } from '../../../../global/core/dataset/utils'; import { mongoSessionRun } from '../../../common/mongo/sessionRun'; import { createTrainingUsage } from '../../../support/wallet/usage/controller'; import { UsageSourceEnum } from '@fastgpt/global/support/wallet/usage/constants'; -import { getLLMModel, getEmbeddingModel } from '../../ai/model'; +import { getLLMModel, getEmbeddingModel, getVlmModel } from '../../ai/model'; import { pushDataListToTrainingQueue } from '../training/controller'; import { MongoImage } from '../../../common/file/image/schema'; import { hashStr } from '@fastgpt/global/common/string/tools'; import { addDays } from 'date-fns'; import { MongoDatasetDataText } from '../data/dataTextSchema'; +import { retryFn } from '@fastgpt/global/common/system/utils'; +import { getTrainingModeByCollection } from './utils'; export const createCollectionAndInsertData = async ({ dataset, @@ -32,6 +34,7 @@ export const createCollectionAndInsertData = async ({ relatedId, createCollectionParams, isQAImport = false, + billId, session }: { dataset: DatasetSchemaType; @@ -40,13 +43,21 @@ export const createCollectionAndInsertData = async ({ createCollectionParams: CreateOneCollectionParams; isQAImport?: boolean; + billId?: string; session?: ClientSession; }) => { + // Adapter 4.9.0 + if (createCollectionParams.trainingType === DatasetCollectionDataProcessModeEnum.auto) { + createCollectionParams.trainingType = DatasetCollectionDataProcessModeEnum.chunk; + createCollectionParams.autoIndexes = true; + } + const teamId = createCollectionParams.teamId; const tmbId = createCollectionParams.tmbId; // Chunk split params - const trainingType = createCollectionParams.trainingType || TrainingModeEnum.chunk; - const chunkSize = createCollectionParams.chunkSize; + const trainingType = + createCollectionParams.trainingType || DatasetCollectionDataProcessModeEnum.chunk; + const chunkSize = createCollectionParams.chunkSize || 512; const chunkSplitter = createCollectionParams.chunkSplitter; const qaPrompt = createCollectionParams.qaPrompt; const usageName = createCollectionParams.name; @@ -55,7 +66,7 @@ export const createCollectionAndInsertData = async ({ const chunks = rawText2Chunks({ rawText, chunkLen: chunkSize, - overlapRatio: trainingType === TrainingModeEnum.chunk ? 0.2 : 0, + overlapRatio: trainingType === DatasetCollectionDataProcessModeEnum.chunk ? 0.2 : 0, customReg: chunkSplitter ? [chunkSplitter] : [], isQAImport }); @@ -63,7 +74,14 @@ export const createCollectionAndInsertData = async ({ // 2. auth limit await checkDatasetLimit({ teamId, - insertLen: predictDataLimitLength(trainingType, chunks) + insertLen: predictDataLimitLength( + getTrainingModeByCollection({ + trainingType, + autoIndexes: createCollectionParams.autoIndexes, + imageIndex: createCollectionParams.imageIndex + }), + chunks + ) }); const fn = async (session: ClientSession) => { @@ -88,15 +106,20 @@ export const createCollectionAndInsertData = async ({ }); // 4. create training bill - const { billId } = await createTrainingUsage({ - teamId, - tmbId, - appName: usageName, - billSource: UsageSourceEnum.training, - vectorModel: getEmbeddingModel(dataset.vectorModel)?.name, - agentModel: getLLMModel(dataset.agentModel)?.name, - session - }); + const traingBillId = await (async () => { + if (billId) return billId; + const { billId: newBillId } = await createTrainingUsage({ + teamId, + tmbId, + appName: usageName, + billSource: UsageSourceEnum.training, + vectorModel: getEmbeddingModel(dataset.vectorModel)?.name, + agentModel: getLLMModel(dataset.agentModel)?.name, + vllmModel: getVlmModel(dataset.vlmModel)?.name, + session + }); + return newBillId; + })(); // 5. insert to training queue const insertResults = await pushDataListToTrainingQueue({ @@ -106,9 +129,14 @@ export const createCollectionAndInsertData = async ({ collectionId, agentModel: dataset.agentModel, vectorModel: dataset.vectorModel, - trainingMode: trainingType, + vlmModel: dataset.vlmModel, + mode: getTrainingModeByCollection({ + trainingType, + autoIndexes: createCollectionParams.autoIndexes, + imageIndex: createCollectionParams.imageIndex + }), prompt: qaPrompt, - billId, + billId: traingBillId, data: chunks.map((item, index) => ({ ...item, chunkIndex: index @@ -160,10 +188,15 @@ export async function createOneCollection({ datasetId, type, - trainingType = TrainingModeEnum.chunk, - chunkSize = 512, - chunkSplitter, - qaPrompt, + createTime, + updateTime, + + hashRawText, + rawTextLength, + metadata = {}, + tags, + + nextSyncTime, fileId, rawLink, @@ -171,15 +204,18 @@ export async function createOneCollection({ externalFileUrl, apiFileId, - hashRawText, - rawTextLength, - metadata = {}, - session, - tags, + // Parse settings + customPdfParse, + imageIndex, - createTime, - updateTime, - nextSyncTime + // Chunk settings + trainingType = DatasetCollectionDataProcessModeEnum.chunk, + autoIndexes, + chunkSize = 512, + chunkSplitter, + qaPrompt, + + session }: CreateOneCollectionParams) { // Create collection tags const collectionTags = await createOrGetCollectionTags({ tags, teamId, datasetId, session }); @@ -195,28 +231,34 @@ export async function createOneCollection({ name, type, - trainingType, - chunkSize, - chunkSplitter, - qaPrompt, + rawTextLength, + hashRawText, + tags: collectionTags, metadata, + createTime, + updateTime, + nextSyncTime, + ...(fileId ? { fileId } : {}), ...(rawLink ? { rawLink } : {}), ...(externalFileId ? { externalFileId } : {}), ...(externalFileUrl ? { externalFileUrl } : {}), ...(apiFileId ? { apiFileId } : {}), - rawTextLength, - hashRawText, - tags: collectionTags, + // Parse settings + customPdfParse, + imageIndex, - createTime, - updateTime, - nextSyncTime + // Chunk settings + trainingType, + autoIndexes, + chunkSize, + chunkSplitter, + qaPrompt } ], - { session } + { session, ordered: true } ); return collection; @@ -227,8 +269,14 @@ export const delCollectionRelatedSource = async ({ collections, session }: { - collections: DatasetCollectionSchemaType[]; - session: ClientSession; + collections: { + teamId: string; + fileId?: string; + metadata?: { + relatedImgId?: string; + }; + }[]; + session?: ClientSession; }) => { if (collections.length === 0) return; @@ -259,66 +307,13 @@ export const delCollectionRelatedSource = async ({ export async function delCollection({ collections, session, - delRelatedSource -}: { - collections: DatasetCollectionSchemaType[]; - session: ClientSession; - delRelatedSource: boolean; -}) { - if (collections.length === 0) return; - - const teamId = collections[0].teamId; - - if (!teamId) return Promise.reject('teamId is not exist'); - - const datasetIds = Array.from(new Set(collections.map((item) => String(item.datasetId)))); - const collectionIds = collections.map((item) => String(item._id)); - - // Delete training data - await MongoDatasetTraining.deleteMany({ - teamId, - datasetIds: { $in: datasetIds }, - collectionId: { $in: collectionIds } - }); - - /* file and imgs */ - if (delRelatedSource) { - await delCollectionRelatedSource({ collections, session }); - } - - // Delete dataset_datas - await MongoDatasetData.deleteMany( - { teamId, datasetIds: { $in: datasetIds }, collectionId: { $in: collectionIds } }, - { session } - ); - // Delete dataset_data_texts - await MongoDatasetDataText.deleteMany( - { teamId, datasetIds: { $in: datasetIds }, collectionId: { $in: collectionIds } }, - { session } - ); - - // delete collections - await MongoDatasetCollection.deleteMany( - { - teamId, - _id: { $in: collectionIds } - }, - { session } - ); - - // no session delete: delete files, vector data - await deleteDatasetDataVector({ teamId, datasetIds, collectionIds }); -} - -/** - * delete delOnlyCollection - */ -export async function delOnlyCollection({ - collections, - session + delImg = true, + delFile = true }: { collections: DatasetCollectionSchemaType[]; session: ClientSession; + delImg: boolean; + delFile: boolean; }) { if (collections.length === 0) return; @@ -329,28 +324,55 @@ export async function delOnlyCollection({ const datasetIds = Array.from(new Set(collections.map((item) => String(item.datasetId)))); const collectionIds = collections.map((item) => String(item._id)); - // delete training data - await MongoDatasetTraining.deleteMany({ - teamId, - datasetIds: { $in: datasetIds }, - collectionId: { $in: collectionIds } + await retryFn(async () => { + await Promise.all([ + // Delete training data + MongoDatasetTraining.deleteMany({ + teamId, + datasetId: { $in: datasetIds }, + collectionId: { $in: collectionIds } + }), + // Delete dataset_data_texts + MongoDatasetDataText.deleteMany({ + teamId, + datasetId: { $in: datasetIds }, + collectionId: { $in: collectionIds } + }), + // Delete dataset_datas + MongoDatasetData.deleteMany({ + teamId, + datasetId: { $in: datasetIds }, + collectionId: { $in: collectionIds } + }), + ...(delImg + ? [ + delImgByRelatedId({ + teamId, + relateIds: collections + .map((item) => item?.metadata?.relatedImgId || '') + .filter(Boolean) + }) + ] + : []), + ...(delFile + ? [ + delFileByFileIdList({ + bucketName: BucketNameEnum.dataset, + fileIdList: collections.map((item) => item?.fileId || '').filter(Boolean) + }) + ] + : []), + // Delete vector data + deleteDatasetDataVector({ teamId, datasetIds, collectionIds }) + ]); + + // delete collections + await MongoDatasetCollection.deleteMany( + { + teamId, + _id: { $in: collectionIds } + }, + { session } + ); }); - - // delete dataset.datas - await MongoDatasetData.deleteMany( - { teamId, datasetIds: { $in: datasetIds }, collectionId: { $in: collectionIds } }, - { session } - ); - - // delete collections - await MongoDatasetCollection.deleteMany( - { - teamId, - _id: { $in: collectionIds } - }, - { session } - ); - - // no session delete: delete files, vector data - await deleteDatasetDataVector({ teamId, datasetIds, collectionIds }); } diff --git a/packages/service/core/dataset/collection/schema.ts b/packages/service/core/dataset/collection/schema.ts index da13ed8aefc1..7e1686f958f4 100644 --- a/packages/service/core/dataset/collection/schema.ts +++ b/packages/service/core/dataset/collection/schema.ts @@ -1,7 +1,10 @@ import { connectionMongo, getMongoModel } from '../../../common/mongo'; const { Schema, model, models } = connectionMongo; import { DatasetCollectionSchemaType } from '@fastgpt/global/core/dataset/type.d'; -import { TrainingTypeMap, DatasetCollectionTypeMap } from '@fastgpt/global/core/dataset/constants'; +import { + DatasetCollectionTypeMap, + DatasetCollectionDataProcessModeEnum +} from '@fastgpt/global/core/dataset/constants'; import { DatasetCollectionName } from '../schema'; import { TeamCollectionName, @@ -31,6 +34,8 @@ const DatasetCollectionSchema = new Schema({ ref: DatasetCollectionName, required: true }, + + // Basic info type: { type: String, enum: Object.keys(DatasetCollectionTypeMap), @@ -40,6 +45,11 @@ const DatasetCollectionSchema = new Schema({ type: String, required: true }, + tags: { + type: [String], + default: [] + }, + createTime: { type: Date, default: () => new Date() @@ -48,33 +58,8 @@ const DatasetCollectionSchema = new Schema({ type: Date, default: () => new Date() }, - forbid: { - type: Boolean, - default: false - }, - - // chunk filed - trainingType: { - type: String, - enum: Object.keys(TrainingTypeMap) - }, - chunkSize: { - type: Number, - required: true - }, - chunkSplitter: { - type: String - }, - qaPrompt: { - type: String - }, - ocrParse: Boolean, - - tags: { - type: [String], - default: [] - }, + // Metadata // local file collection fileId: { type: Schema.Types.ObjectId, @@ -82,22 +67,39 @@ const DatasetCollectionSchema = new Schema({ }, // web link collection rawLink: String, - // api collection + // Api collection apiFileId: String, - // external collection + // external collection(Abandoned) externalFileId: String, externalFileUrl: String, // external import url - // next sync time - nextSyncTime: Date, - - // metadata rawTextLength: Number, hashRawText: String, metadata: { type: Object, default: {} - } + }, + + forbid: Boolean, + // next sync time + nextSyncTime: Date, + + // Parse settings + customPdfParse: Boolean, + + // Chunk settings + imageIndex: Boolean, + autoIndexes: Boolean, + trainingType: { + type: String, + enum: Object.values(DatasetCollectionDataProcessModeEnum) + }, + chunkSize: { + type: Number, + required: true + }, + chunkSplitter: String, + qaPrompt: String }); DatasetCollectionSchema.virtual('dataset', { diff --git a/packages/service/core/dataset/collection/utils.ts b/packages/service/core/dataset/collection/utils.ts index 9bf9a22621b0..01051ca77914 100644 --- a/packages/service/core/dataset/collection/utils.ts +++ b/packages/service/core/dataset/collection/utils.ts @@ -2,12 +2,17 @@ import { MongoDatasetCollection } from './schema'; import { ClientSession } from '../../../common/mongo'; import { MongoDatasetCollectionTags } from '../tag/schema'; import { readFromSecondary } from '../../../common/mongo/utils'; -import { CollectionWithDatasetType } from '@fastgpt/global/core/dataset/type'; import { + CollectionWithDatasetType, + DatasetCollectionSchemaType +} from '@fastgpt/global/core/dataset/type'; +import { + DatasetCollectionDataProcessModeEnum, DatasetCollectionSyncResultEnum, DatasetCollectionTypeEnum, DatasetSourceReadTypeEnum, - DatasetTypeEnum + DatasetTypeEnum, + TrainingModeEnum } from '@fastgpt/global/core/dataset/constants'; import { DatasetErrEnum } from '@fastgpt/global/common/error/code/dataset'; import { readDatasetSourceRawText } from '../read'; @@ -97,7 +102,7 @@ export const createOrGetCollectionTags = async ({ datasetId, tag: tagContent })), - { session } + { session, ordered: true } ); return [...existingTags.map((tag) => tag._id), ...newTags.map((tag) => tag._id)]; @@ -160,6 +165,7 @@ export const syncCollection = async (collection: CollectionWithDatasetType) => { })(); const rawText = await readDatasetSourceRawText({ teamId: collection.teamId, + tmbId: collection.tmbId, ...sourceReadType }); @@ -174,6 +180,14 @@ export const syncCollection = async (collection: CollectionWithDatasetType) => { } await mongoSessionRun(async (session) => { + // Delete old collection + await delCollection({ + collections: [collection], + delImg: false, + delFile: false, + session + }); + // Create new collection await createCollectionAndInsertData({ session, @@ -208,14 +222,28 @@ export const syncCollection = async (collection: CollectionWithDatasetType) => { updateTime: new Date() } }); - - // Delete old collection - await delCollection({ - collections: [collection], - delRelatedSource: false, - session - }); }); return DatasetCollectionSyncResultEnum.success; }; + +/* + QA: 独立进程 + Chunk: Image Index -> Auto index -> chunk index +*/ +export const getTrainingModeByCollection = (collection: { + trainingType: DatasetCollectionSchemaType['trainingType']; + autoIndexes?: DatasetCollectionSchemaType['autoIndexes']; + imageIndex?: DatasetCollectionSchemaType['imageIndex']; +}) => { + if (collection.trainingType === DatasetCollectionDataProcessModeEnum.qa) { + return TrainingModeEnum.qa; + } + if (collection.imageIndex && global.feConfigs?.isPlus) { + return TrainingModeEnum.image; + } + if (collection.autoIndexes && global.feConfigs?.isPlus) { + return TrainingModeEnum.auto; + } + return TrainingModeEnum.chunk; +}; diff --git a/packages/service/core/dataset/controller.ts b/packages/service/core/dataset/controller.ts index 96f6523e7e01..06be050a9b37 100644 --- a/packages/service/core/dataset/controller.ts +++ b/packages/service/core/dataset/controller.ts @@ -7,6 +7,8 @@ import { MongoDatasetTraining } from './training/schema'; import { MongoDatasetData } from './data/schema'; import { deleteDatasetDataVector } from '../../common/vectorStore/controller'; import { MongoDatasetDataText } from './data/dataTextSchema'; +import { DatasetErrEnum } from '@fastgpt/global/common/error/code/dataset'; +import { retryFn } from '@fastgpt/global/common/system/utils'; /* ============= dataset ========== */ /* find all datasetId by top datasetId */ @@ -54,7 +56,7 @@ export async function getCollectionWithDataset(collectionId: string) { .populate<{ dataset: DatasetSchemaType }>('dataset') .lean(); if (!data) { - return Promise.reject('Collection is not exist'); + return Promise.reject(DatasetErrEnum.unExistCollection); } return data; } @@ -77,40 +79,39 @@ export async function delDatasetRelevantData({ const datasetIds = datasets.map((item) => item._id); - // delete training data - await MongoDatasetTraining.deleteMany({ - teamId, - datasetId: { $in: datasetIds } - }); - // Get _id, teamId, fileId, metadata.relatedImgId for all collections const collections = await MongoDatasetCollection.find( { teamId, datasetId: { $in: datasetIds } }, - '_id teamId datasetId fileId metadata', - { session } + '_id teamId datasetId fileId metadata' ).lean(); - // Delete Image and file - await delCollectionRelatedSource({ collections, session }); + await retryFn(async () => { + await Promise.all([ + // delete training data + MongoDatasetTraining.deleteMany({ + teamId, + datasetId: { $in: datasetIds } + }), + //Delete dataset_data_texts + MongoDatasetDataText.deleteMany({ + teamId, + datasetId: { $in: datasetIds } + }), + //delete dataset_datas + MongoDatasetData.deleteMany({ teamId, datasetId: { $in: datasetIds } }), + // Delete Image and file + delCollectionRelatedSource({ collections }), + // Delete vector data + deleteDatasetDataVector({ teamId, datasetIds }) + ]); + }); // delete collections await MongoDatasetCollection.deleteMany({ teamId, datasetId: { $in: datasetIds } }).session(session); - - // No session delete: - // Delete dataset_data_texts - await MongoDatasetDataText.deleteMany({ - teamId, - datasetId: { $in: datasetIds } - }); - // delete dataset_datas - await MongoDatasetData.deleteMany({ teamId, datasetId: { $in: datasetIds } }); - - // Delete vector data - await deleteDatasetDataVector({ teamId, datasetIds }); } diff --git a/packages/service/core/dataset/data/dataTextSchema.ts b/packages/service/core/dataset/data/dataTextSchema.ts index da06a80a4416..ae85b3ef361f 100644 --- a/packages/service/core/dataset/data/dataTextSchema.ts +++ b/packages/service/core/dataset/data/dataTextSchema.ts @@ -1,6 +1,6 @@ import { connectionMongo, getMongoModel } from '../../../common/mongo'; const { Schema } = connectionMongo; -import { DatasetDataSchemaType } from '@fastgpt/global/core/dataset/type.d'; +import { DatasetDataTextSchemaType } from '@fastgpt/global/core/dataset/type.d'; import { TeamCollectionName } from '@fastgpt/global/support/user/team/constant'; import { DatasetCollectionName } from '../schema'; import { DatasetColCollectionName } from '../collection/schema'; @@ -40,12 +40,13 @@ try { default_language: 'none' } ); + DatasetDataTextSchema.index({ teamId: 1, datasetId: 1, collectionId: 1 }); DatasetDataTextSchema.index({ dataId: 1 }, { unique: true }); } catch (error) { console.log(error); } -export const MongoDatasetDataText = getMongoModel( +export const MongoDatasetDataText = getMongoModel( DatasetDataTextCollectionName, DatasetDataTextSchema ); diff --git a/packages/service/core/dataset/data/schema.ts b/packages/service/core/dataset/data/schema.ts index 85dd8a7d22eb..bdba3b87c008 100644 --- a/packages/service/core/dataset/data/schema.ts +++ b/packages/service/core/dataset/data/schema.ts @@ -7,6 +7,7 @@ import { } from '@fastgpt/global/support/user/team/constant'; import { DatasetCollectionName } from '../schema'; import { DatasetColCollectionName } from '../collection/schema'; +import { DatasetDataIndexTypeEnum } from '@fastgpt/global/core/dataset/data/constants'; export const DatasetDataCollectionName = 'dataset_datas'; @@ -42,9 +43,14 @@ const DatasetDataSchema = new Schema({ indexes: { type: [ { + // Abandon defaultIndex: { - type: Boolean, - default: false + type: Boolean + }, + type: { + type: String, + enum: Object.values(DatasetDataIndexTypeEnum), + default: DatasetDataIndexTypeEnum.custom }, dataId: { type: String, diff --git a/packages/service/core/dataset/read.ts b/packages/service/core/dataset/read.ts index 5d84e065b074..7f7125290deb 100644 --- a/packages/service/core/dataset/read.ts +++ b/packages/service/core/dataset/read.ts @@ -13,11 +13,15 @@ import { POST } from '../../common/api/plusRequest'; export const readFileRawTextByUrl = async ({ teamId, + tmbId, url, + customPdfParse, relatedId }: { teamId: string; + tmbId: string; url: string; + customPdfParse?: boolean; relatedId: string; // externalFileId / apiFileId }) => { const response = await axios({ @@ -30,8 +34,11 @@ export const readFileRawTextByUrl = async ({ const buffer = Buffer.from(response.data, 'binary'); const { rawText } = await readRawContentByFileBuffer({ + customPdfParse, + isQAImport: false, extension, teamId, + tmbId, buffer, encoding: 'utf-8', metadata: { @@ -49,6 +56,7 @@ export const readFileRawTextByUrl = async ({ */ export const readDatasetSourceRawText = async ({ teamId, + tmbId, type, sourceId, isQAImport, @@ -56,11 +64,14 @@ export const readDatasetSourceRawText = async ({ externalFileId, apiServer, feishuServer, - yuqueServer + yuqueServer, + customPdfParse }: { teamId: string; + tmbId: string; type: DatasetSourceReadTypeEnum; sourceId: string; + customPdfParse?: boolean; isQAImport?: boolean; // csv data selector?: string; // link selector @@ -72,9 +83,11 @@ export const readDatasetSourceRawText = async ({ if (type === DatasetSourceReadTypeEnum.fileLocal) { const { rawText } = await readFileContentFromMongo({ teamId, + tmbId, bucketName: BucketNameEnum.dataset, fileId: sourceId, - isQAImport + isQAImport, + customPdfParse }); return rawText; } else if (type === DatasetSourceReadTypeEnum.link) { @@ -88,8 +101,10 @@ export const readDatasetSourceRawText = async ({ if (!externalFileId) return Promise.reject('FileId not found'); const rawText = await readFileRawTextByUrl({ teamId, + tmbId, url: sourceId, - relatedId: externalFileId + relatedId: externalFileId, + customPdfParse }); return rawText; } else if (type === DatasetSourceReadTypeEnum.apiFile) { @@ -98,7 +113,8 @@ export const readDatasetSourceRawText = async ({ feishuServer, yuqueServer, apiFileId: sourceId, - teamId + teamId, + tmbId }); return rawText; } @@ -110,16 +126,18 @@ export const readApiServerFileContent = async ({ feishuServer, yuqueServer, apiFileId, - teamId + teamId, + tmbId }: { apiServer?: APIFileServer; feishuServer?: FeishuServer; yuqueServer?: YuqueServer; apiFileId: string; teamId: string; + tmbId: string; }) => { if (apiServer) { - return useApiDatasetRequest({ apiServer }).getFileContent({ teamId, apiFileId }); + return useApiDatasetRequest({ apiServer }).getFileContent({ teamId, tmbId, apiFileId }); } if (feishuServer || yuqueServer) { diff --git a/packages/service/core/dataset/schema.ts b/packages/service/core/dataset/schema.ts index f8f80ef4deab..22f79fd2553a 100644 --- a/packages/service/core/dataset/schema.ts +++ b/packages/service/core/dataset/schema.ts @@ -67,6 +67,7 @@ const DatasetSchema = new Schema({ required: true, default: 'gpt-4o-mini' }, + vlmModel: String, intro: { type: String, default: '' diff --git a/packages/service/core/dataset/search/controller.ts b/packages/service/core/dataset/search/controller.ts index 3554a452a4c7..87350f25dcdf 100644 --- a/packages/service/core/dataset/search/controller.ts +++ b/packages/service/core/dataset/search/controller.ts @@ -787,6 +787,7 @@ export const defaultSearchDatasetData = async ({ ...props }: DefaultSearchDatasetDataProps): Promise => { const query = props.queries[0]; + const histories = props.histories; const extensionModel = datasetSearchUsingExtensionQuery ? getLLMModel(datasetSearchExtensionModel) @@ -796,7 +797,8 @@ export const defaultSearchDatasetData = async ({ await datasetSearchQueryExtension({ query, extensionModel, - extensionBg: datasetSearchExtensionBg + extensionBg: datasetSearchExtensionBg, + histories }); const result = await searchDatasetData({ diff --git a/packages/service/core/dataset/training/controller.ts b/packages/service/core/dataset/training/controller.ts index a8bbe9417aaf..d740eec551af 100644 --- a/packages/service/core/dataset/training/controller.ts +++ b/packages/service/core/dataset/training/controller.ts @@ -1,16 +1,17 @@ import { MongoDatasetTraining } from './schema'; import type { PushDatasetDataChunkProps, - PushDatasetDataProps, PushDatasetDataResponse } from '@fastgpt/global/core/dataset/api.d'; import { TrainingModeEnum } from '@fastgpt/global/core/dataset/constants'; import { simpleText } from '@fastgpt/global/common/string/tools'; import { ClientSession } from '../../../common/mongo'; -import { getLLMModel, getEmbeddingModel } from '../../ai/model'; +import { getLLMModel, getEmbeddingModel, getVlmModel } from '../../ai/model'; import { addLog } from '../../../common/system/log'; import { getCollectionWithDataset } from '../controller'; import { mongoSessionRun } from '../../../common/mongo/sessionRun'; +import { PushDataToTrainingQueueProps } from '@fastgpt/global/core/dataset/training/type'; +import { i18nT } from '../../../../web/i18n/utils'; export const lockTrainingDataByTeamId = async (teamId: string): Promise => { try { @@ -28,20 +29,17 @@ export const lockTrainingDataByTeamId = async (teamId: string): Promise => export const pushDataListToTrainingQueueByCollectionId = async ({ collectionId, ...props -}: { - teamId: string; - tmbId: string; - session?: ClientSession; -} & PushDatasetDataProps) => { +}: Omit) => { const { - dataset: { _id: datasetId, agentModel, vectorModel } + dataset: { _id: datasetId, agentModel, vectorModel, vlmModel } } = await getCollectionWithDataset(collectionId); return pushDataListToTrainingQueue({ ...props, datasetId, collectionId, + vectorModel, agentModel, - vectorModel + vlmModel }); }; @@ -52,30 +50,30 @@ export async function pushDataListToTrainingQueue({ collectionId, agentModel, vectorModel, + vlmModel, data, prompt, billId, - trainingMode = TrainingModeEnum.chunk, + mode = TrainingModeEnum.chunk, session -}: { - teamId: string; - tmbId: string; - datasetId: string; - agentModel: string; - vectorModel: string; - session?: ClientSession; -} & PushDatasetDataProps): Promise { - const { model, maxToken, weight } = await (async () => { - const agentModelData = getLLMModel(agentModel); - if (!agentModelData) { - return Promise.reject(`File model ${agentModel} is inValid`); - } - const vectorModelData = getEmbeddingModel(vectorModel); - if (!vectorModelData) { - return Promise.reject(`Vector model ${vectorModel} is inValid`); +}: PushDataToTrainingQueueProps): Promise { + const getImageChunkMode = (data: PushDatasetDataChunkProps, mode: TrainingModeEnum) => { + if (mode !== TrainingModeEnum.image) return mode; + // 检查内容中,是否包含 ![](xxx) 的图片格式 + const text = data.q + data.a || ''; + const regex = /!\[\]\((.*?)\)/g; + const match = text.match(regex); + if (match) { + return TrainingModeEnum.image; } - - if (trainingMode === TrainingModeEnum.chunk) { + return mode; + }; + const { model, maxToken, weight } = await (async () => { + if (mode === TrainingModeEnum.chunk) { + const vectorModelData = getEmbeddingModel(vectorModel); + if (!vectorModelData) { + return Promise.reject(i18nT('common:error_embedding_not_config')); + } return { maxToken: vectorModelData.maxToken * 1.5, model: vectorModelData.model, @@ -83,7 +81,11 @@ export async function pushDataListToTrainingQueue({ }; } - if (trainingMode === TrainingModeEnum.qa || trainingMode === TrainingModeEnum.auto) { + if (mode === TrainingModeEnum.qa || mode === TrainingModeEnum.auto) { + const agentModelData = getLLMModel(agentModel); + if (!agentModelData) { + return Promise.reject(i18nT('common:error_llm_not_config')); + } return { maxToken: agentModelData.maxContext * 0.8, model: agentModelData.model, @@ -91,8 +93,24 @@ export async function pushDataListToTrainingQueue({ }; } - return Promise.reject(`Training mode "${trainingMode}" is inValid`); + if (mode === TrainingModeEnum.image) { + const vllmModelData = getVlmModel(vlmModel); + if (!vllmModelData) { + return Promise.reject(i18nT('common:error_vlm_not_config')); + } + return { + maxToken: vllmModelData.maxContext * 0.8, + model: vllmModelData.model, + weight: 0 + }; + } + + return Promise.reject(`Training mode "${mode}" is inValid`); })(); + // Filter redundant params + if (mode === TrainingModeEnum.chunk || mode === TrainingModeEnum.auto) { + prompt = undefined; + } // filter repeat or equal content const set = new Set(); @@ -158,7 +176,7 @@ export async function pushDataListToTrainingQueue({ datasetId, collectionId, billId, - mode: trainingMode, + mode: getImageChunkMode(item, mode), prompt, model, q: item.q, diff --git a/packages/service/core/dataset/training/schema.ts b/packages/service/core/dataset/training/schema.ts index 48e01613a7e8..34044674db97 100644 --- a/packages/service/core/dataset/training/schema.ts +++ b/packages/service/core/dataset/training/schema.ts @@ -1,14 +1,15 @@ /* 模型的知识库 */ -import { connectionMongo, getMongoModel, type Model } from '../../../common/mongo'; -const { Schema, model, models } = connectionMongo; +import { connectionMongo, getMongoModel } from '../../../common/mongo'; +const { Schema } = connectionMongo; import { DatasetTrainingSchemaType } from '@fastgpt/global/core/dataset/type'; -import { TrainingTypeMap } from '@fastgpt/global/core/dataset/constants'; +import { TrainingModeEnum } from '@fastgpt/global/core/dataset/constants'; import { DatasetColCollectionName } from '../collection/schema'; import { DatasetCollectionName } from '../schema'; import { TeamCollectionName, TeamMemberCollectionName } from '@fastgpt/global/support/user/team/constant'; +import { DatasetDataIndexTypeEnum } from '@fastgpt/global/core/dataset/data/constants'; export const DatasetTrainingCollectionName = 'dataset_trainings'; @@ -25,7 +26,6 @@ const TrainingDataSchema = new Schema({ }, datasetId: { type: Schema.Types.ObjectId, - ref: DatasetCollectionName, required: true }, collectionId: { @@ -33,15 +33,13 @@ const TrainingDataSchema = new Schema({ ref: DatasetColCollectionName, required: true }, - billId: { - // concat bill - type: String - }, + billId: String, mode: { type: String, - enum: Object.keys(TrainingTypeMap), + enum: Object.values(TrainingModeEnum), required: true }, + expireAt: { // It will be deleted after 7 days type: Date, @@ -88,6 +86,10 @@ const TrainingDataSchema = new Schema({ indexes: { type: [ { + type: { + type: String, + enum: Object.values(DatasetDataIndexTypeEnum) + }, text: { type: String, required: true @@ -98,6 +100,19 @@ const TrainingDataSchema = new Schema({ } }); +TrainingDataSchema.virtual('dataset', { + ref: DatasetCollectionName, + localField: 'datasetId', + foreignField: '_id', + justOne: true +}); +TrainingDataSchema.virtual('collection', { + ref: DatasetColCollectionName, + localField: 'collectionId', + foreignField: '_id', + justOne: true +}); + try { // lock training data(teamId); delete training data TrainingDataSchema.index({ teamId: 1, datasetId: 1 }); diff --git a/packages/service/core/workflow/dispatch/agent/runTool/index.ts b/packages/service/core/workflow/dispatch/agent/runTool/index.ts index c7aeabe31754..b10b57eac9eb 100644 --- a/packages/service/core/workflow/dispatch/agent/runTool/index.ts +++ b/packages/service/core/workflow/dispatch/agent/runTool/index.ts @@ -1,6 +1,7 @@ import { NodeOutputKeyEnum } from '@fastgpt/global/core/workflow/constants'; import { DispatchNodeResponseKeyEnum } from '@fastgpt/global/core/workflow/runtime/constants'; import type { + ChatDispatchProps, DispatchNodeResultType, RuntimeNodeItemType } from '@fastgpt/global/core/workflow/runtime/type'; @@ -46,7 +47,7 @@ export const dispatchRunTools = async (props: DispatchToolModuleProps): Promise< query, requestOrigin, chatConfig, - runningAppInfo: { teamId }, + runningUserInfo, externalProvider, params: { model, @@ -54,7 +55,8 @@ export const dispatchRunTools = async (props: DispatchToolModuleProps): Promise< userChatInput, history = 6, fileUrlList: fileLinks, - aiChatVision + aiChatVision, + aiChatReasoning } } = props; @@ -62,6 +64,9 @@ export const dispatchRunTools = async (props: DispatchToolModuleProps): Promise< const useVision = aiChatVision && toolModel.vision; const chatHistories = getHistories(history, histories); + props.params.aiChatVision = aiChatVision && toolModel.vision; + props.params.aiChatReasoning = aiChatReasoning && toolModel.reasoning; + const toolNodeIds = filterToolNodeIdByEdges({ nodeId, edges: runtimeEdges }); // Gets the module to which the tool is connected @@ -99,10 +104,11 @@ export const dispatchRunTools = async (props: DispatchToolModuleProps): Promise< const globalFiles = chatValue2RuntimePrompt(query).files; const { documentQuoteText, userFiles } = await getMultiInput({ + runningUserInfo, histories: chatHistories, requestOrigin, maxFiles: chatConfig?.fileSelectConfig?.maxFiles || 20, - teamId, + customPdfParse: chatConfig?.fileSelectConfig?.customPdfParse, fileLinks, inputFiles: globalFiles, hasReadFilesTool @@ -289,19 +295,21 @@ export const dispatchRunTools = async (props: DispatchToolModuleProps): Promise< }; const getMultiInput = async ({ + runningUserInfo, histories, fileLinks, requestOrigin, maxFiles, - teamId, + customPdfParse, inputFiles, hasReadFilesTool }: { + runningUserInfo: ChatDispatchProps['runningUserInfo']; histories: ChatItemType[]; fileLinks?: string[]; requestOrigin?: string; maxFiles: number; - teamId: string; + customPdfParse?: boolean; inputFiles: UserChatItemValueItemType['file'][]; hasReadFilesTool: boolean; }) => { @@ -329,7 +337,9 @@ const getMultiInput = async ({ urls, requestOrigin, maxFiles, - teamId + customPdfParse, + teamId: runningUserInfo.teamId, + tmbId: runningUserInfo.tmbId }); return { diff --git a/packages/service/core/workflow/dispatch/agent/runTool/promptCall.ts b/packages/service/core/workflow/dispatch/agent/runTool/promptCall.ts index 58e95a059303..c5c0cb4bd6d0 100644 --- a/packages/service/core/workflow/dispatch/agent/runTool/promptCall.ts +++ b/packages/service/core/workflow/dispatch/agent/runTool/promptCall.ts @@ -24,7 +24,12 @@ import { import { AIChatItemType } from '@fastgpt/global/core/chat/type'; import { GPTMessages2Chats } from '@fastgpt/global/core/chat/adapt'; import { formatToolResponse, initToolCallEdges, initToolNodes } from './utils'; -import { computedMaxToken, llmCompletionsBodyFormat } from '../../../../ai/utils'; +import { + computedMaxToken, + llmCompletionsBodyFormat, + parseReasoningContent, + parseReasoningStreamContent +} from '../../../../ai/utils'; import { WorkflowResponseType } from '../../type'; import { toolValueTypeList } from '@fastgpt/global/core/workflow/constants'; import { WorkflowInteractiveResponseType } from '@fastgpt/global/core/workflow/template/system/interactive/type'; @@ -58,6 +63,7 @@ export const runToolWithPromptCall = async ( temperature, maxToken, aiChatVision, + aiChatReasoning, aiChatTopP, aiChatStopSign, aiChatResponseFormat, @@ -216,7 +222,7 @@ export const runToolWithPromptCall = async ( const [requestMessages] = await Promise.all([ loadRequestMessages({ messages: filterMessages, - useVision: toolModel.vision && aiChatVision, + useVision: aiChatVision, origin: requestOrigin }) ]); @@ -251,22 +257,46 @@ export const runToolWithPromptCall = async ( } }); - const answer = await (async () => { + const { answer, reasoning } = await (async () => { if (res && isStreamResponse) { - const { answer } = await streamResponse({ + const { answer, reasoning } = await streamResponse({ res, toolNodes, stream: aiResponse, - workflowStreamResponse + workflowStreamResponse, + aiChatReasoning }); - return answer; + return { answer, reasoning }; } else { - const result = aiResponse as ChatCompletion; + const content = aiResponse.choices?.[0]?.message?.content || ''; + const reasoningContent: string = aiResponse.choices?.[0]?.message?.reasoning_content || ''; + + // API already parse reasoning content + if (reasoningContent || !aiChatReasoning) { + return { + answer: content, + reasoning: reasoningContent + }; + } - return result.choices?.[0]?.message?.content || ''; + const [think, answer] = parseReasoningContent(content); + return { + answer, + reasoning: think + }; } })(); + + if (stream && !isStreamResponse && aiChatReasoning && reasoning) { + workflowStreamResponse?.({ + event: SseResponseEventEnum.fastAnswer, + data: textAdaptGptResponse({ + reasoning_content: reasoning + }) + }); + } + const { answer: replaceAnswer, toolJson } = parseAnswer(answer); if (!answer && !toolJson) { return Promise.reject(getEmptyResponseTip()); @@ -294,11 +324,16 @@ export const runToolWithPromptCall = async ( } // No tool is invoked, indicating that the process is over - const gptAssistantResponse: ChatCompletionAssistantMessageParam = { + const gptAssistantResponse: ChatCompletionMessageParam = { role: ChatCompletionRequestMessageRoleEnum.Assistant, - content: replaceAnswer + content: replaceAnswer, + reasoning_text: reasoning }; - const completeMessages = filterMessages.concat(gptAssistantResponse); + const completeMessages = filterMessages.concat({ + ...gptAssistantResponse, + reasoning_text: undefined + }); + const inputTokens = await countGptMessagesTokens(requestMessages); const outputTokens = await countGptMessagesTokens([gptAssistantResponse]); @@ -379,9 +414,10 @@ export const runToolWithPromptCall = async ( })(); // 合并工具调用的结果,使用 functionCall 格式存储。 - const assistantToolMsgParams: ChatCompletionAssistantMessageParam = { + const assistantToolMsgParams: ChatCompletionMessageParam = { role: ChatCompletionRequestMessageRoleEnum.Assistant, - function_call: toolJson + function_call: toolJson, + reasoning_text: reasoning }; // Only toolCall tokens are counted here, Tool response tokens count towards the next reply @@ -502,12 +538,14 @@ ANSWER: `; async function streamResponse({ res, stream, - workflowStreamResponse + workflowStreamResponse, + aiChatReasoning }: { res: NextApiResponse; toolNodes: ToolNodeItemType[]; stream: StreamChatType; workflowStreamResponse?: WorkflowResponseType; + aiChatReasoning?: boolean; }) { const write = responseWriteController({ res, @@ -515,7 +553,9 @@ async function streamResponse({ }); let startResponseWrite = false; - let textAnswer = ''; + let answer = ''; + let reasoning = ''; + const { parsePart, getStartTagBuffer } = parseReasoningStreamContent(); for await (const part of stream) { if (res.closed) { @@ -523,13 +563,21 @@ async function streamResponse({ break; } - const responseChoice = part.choices?.[0]?.delta; - // console.log(responseChoice, '---==='); + const [reasoningContent, content] = parsePart(part, aiChatReasoning); + answer += content; + reasoning += reasoningContent; - if (responseChoice?.content) { - const content = responseChoice?.content || ''; - textAnswer += content; + if (aiChatReasoning && reasoningContent) { + workflowStreamResponse?.({ + write, + event: SseResponseEventEnum.answer, + data: textAdaptGptResponse({ + reasoning_content: reasoningContent + }) + }); + } + if (content) { if (startResponseWrite) { workflowStreamResponse?.({ write, @@ -538,18 +586,20 @@ async function streamResponse({ text: content }) }); - } else if (textAnswer.length >= 3) { - textAnswer = textAnswer.trim(); - if (textAnswer.startsWith('0')) { + } else if (answer.length >= 3) { + answer = answer.trimStart(); + if (/0(:|:)/.test(answer)) { startResponseWrite = true; + // find first : index - const firstIndex = textAnswer.indexOf(':'); - textAnswer = textAnswer.substring(firstIndex + 1).trim(); + const firstIndex = + answer.indexOf('0:') !== -1 ? answer.indexOf('0:') : answer.indexOf('0:'); + answer = answer.substring(firstIndex + 2).trim(); workflowStreamResponse?.({ write, event: SseResponseEventEnum.answer, data: textAdaptGptResponse({ - text: textAnswer + text: answer }) }); } @@ -557,7 +607,23 @@ async function streamResponse({ } } - return { answer: textAnswer.trim() }; + if (answer === '') { + answer = getStartTagBuffer(); + if (/0(:|:)/.test(answer)) { + // find first : index + const firstIndex = answer.indexOf('0:') !== -1 ? answer.indexOf('0:') : answer.indexOf('0:'); + answer = answer.substring(firstIndex + 2).trim(); + workflowStreamResponse?.({ + write, + event: SseResponseEventEnum.answer, + data: textAdaptGptResponse({ + text: answer + }) + }); + } + } + + return { answer, reasoning }; } const parseAnswer = ( @@ -568,8 +634,7 @@ const parseAnswer = ( } => { str = str.trim(); // 首先,使用正则表达式提取TOOL_ID和TOOL_ARGUMENTS - const prefixReg = /^1(:|:)/; - const answerPrefixReg = /^0(:|:)/; + const prefixReg = /1(:|:)/; if (prefixReg.test(str)) { const toolString = sliceJsonStr(str); @@ -585,13 +650,21 @@ const parseAnswer = ( } }; } catch (error) { - return { - answer: ERROR_TEXT - }; + if (/^1(:|:)/.test(str)) { + return { + answer: ERROR_TEXT + }; + } else { + return { + answer: str + }; + } } } else { + const firstIndex = str.indexOf('0:') !== -1 ? str.indexOf('0:') : str.indexOf('0:'); + const answer = str.substring(firstIndex + 2).trim(); return { - answer: str.replace(answerPrefixReg, '') + answer }; } }; diff --git a/packages/service/core/workflow/dispatch/agent/runTool/type.d.ts b/packages/service/core/workflow/dispatch/agent/runTool/type.d.ts index 64ecd79fcb96..61cb6b217bcf 100644 --- a/packages/service/core/workflow/dispatch/agent/runTool/type.d.ts +++ b/packages/service/core/workflow/dispatch/agent/runTool/type.d.ts @@ -22,6 +22,7 @@ export type DispatchToolModuleProps = ModuleDispatchProps<{ [NodeInputKeyEnum.aiChatTemperature]: number; [NodeInputKeyEnum.aiChatMaxToken]: number; [NodeInputKeyEnum.aiChatVision]?: boolean; + [NodeInputKeyEnum.aiChatReasoning]?: boolean; [NodeInputKeyEnum.aiChatTopP]?: number; [NodeInputKeyEnum.aiChatStopSign]?: string; [NodeInputKeyEnum.aiChatResponseFormat]?: string; diff --git a/packages/service/core/workflow/dispatch/chat/oneapi.ts b/packages/service/core/workflow/dispatch/chat/oneapi.ts index a634e6e98b7a..d5bcd493952c 100644 --- a/packages/service/core/workflow/dispatch/chat/oneapi.ts +++ b/packages/service/core/workflow/dispatch/chat/oneapi.ts @@ -3,18 +3,18 @@ import { filterGPTMessageByMaxContext, loadRequestMessages } from '../../../chat import type { ChatItemType, UserChatItemValueItemType } from '@fastgpt/global/core/chat/type.d'; import { ChatRoleEnum } from '@fastgpt/global/core/chat/constants'; import { SseResponseEventEnum } from '@fastgpt/global/core/workflow/runtime/constants'; -import { - parseReasoningContent, - parseReasoningStreamContent, - textAdaptGptResponse -} from '@fastgpt/global/core/workflow/runtime/utils'; +import { textAdaptGptResponse } from '@fastgpt/global/core/workflow/runtime/utils'; +import { parseReasoningContent, parseReasoningStreamContent } from '../../../ai/utils'; import { createChatCompletion } from '../../../ai/config'; import type { ChatCompletionMessageParam, StreamChatType } from '@fastgpt/global/core/ai/type.d'; import { formatModelChars2Points } from '../../../../support/wallet/usage/utils'; import type { LLMModelItemType } from '@fastgpt/global/core/ai/model.d'; import { postTextCensor } from '../../../../common/api/requestPlusApi'; import { ChatCompletionRequestMessageRoleEnum } from '@fastgpt/global/core/ai/constants'; -import type { DispatchNodeResultType } from '@fastgpt/global/core/workflow/runtime/type'; +import type { + ChatDispatchProps, + DispatchNodeResultType +} from '@fastgpt/global/core/workflow/runtime/type'; import { countGptMessagesTokens } from '../../../../common/string/tiktoken/index'; import { chats2GPTMessages, @@ -72,7 +72,7 @@ export const dispatchChatCompletion = async (props: ChatProps): Promise if (stringQuoteText) { @@ -403,7 +406,9 @@ async function getMultiInput({ urls, requestOrigin, maxFiles, - teamId + customPdfParse, + teamId: runningUserInfo.teamId, + tmbId: runningUserInfo.tmbId }); return { @@ -558,6 +563,15 @@ async function streamResponse({ // if answer is empty, try to get value from startTagBuffer. (Cause: The response content is too short to exceed the minimum parse length) if (answer === '') { answer = getStartTagBuffer(); + if (isResponseAnswerText && answer) { + workflowStreamResponse?.({ + write, + event: SseResponseEventEnum.answer, + data: textAdaptGptResponse({ + text: answer + }) + }); + } } return { answer, reasoning }; diff --git a/packages/service/core/workflow/dispatch/loop/runLoop.ts b/packages/service/core/workflow/dispatch/loop/runLoop.ts index 771a1de19c4d..b3a0d1e4cc0a 100644 --- a/packages/service/core/workflow/dispatch/loop/runLoop.ts +++ b/packages/service/core/workflow/dispatch/loop/runLoop.ts @@ -62,6 +62,7 @@ export const dispatchLoop = async (props: Props): Promise => { const response = await dispatchWorkFlow({ ...props, + variables: newVariables, runtimeEdges: cloneDeep(runtimeEdges) }); diff --git a/packages/service/core/workflow/dispatch/tools/http468.ts b/packages/service/core/workflow/dispatch/tools/http468.ts index cc36e0c0fc78..6df026eaa449 100644 --- a/packages/service/core/workflow/dispatch/tools/http468.ts +++ b/packages/service/core/workflow/dispatch/tools/http468.ts @@ -120,27 +120,145 @@ export const dispatchHttp468Request = async (props: HttpRequestProps): Promise { - const valToStr = (val: any) => { + // Check if the variable is in quotes + const isVariableInQuotes = (text: string, variable: string) => { + const index = text.indexOf(variable); + if (index === -1) return false; + + // 计算变量前面的引号数量 + const textBeforeVar = text.substring(0, index); + const matches = textBeforeVar.match(/"/g) || []; + + // 如果引号数量为奇数,则变量在引号内 + return matches.length % 2 === 1; + }; + const valToStr = (val: any, isQuoted = false) => { if (val === undefined) return 'null'; if (val === null) return 'null'; if (typeof val === 'object') return JSON.stringify(val); if (typeof val === 'string') { + if (isQuoted) { + // Replace newlines with escaped newlines + return val.replace(/\n/g, '\\n').replace(/(? { + // const testData = [ + // // 基本字符串替换 + // { + // body: `{"name":"{{name}}","age":"18"}`, + // variables: [{ key: '{{name}}', value: '测试' }], + // result: `{"name":"测试","age":"18"}` + // }, + // // 特殊字符处理 + // { + // body: `{"text":"{{text}}"}`, + // variables: [{ key: '{{text}}', value: '包含"引号"和\\反斜杠' }], + // result: `{"text":"包含\\"引号\\"和\\反斜杠"}` + // }, + // // 数字类型处理 + // { + // body: `{"count":{{count}},"price":{{price}}}`, + // variables: [ + // { key: '{{count}}', value: '42' }, + // { key: '{{price}}', value: '99.99' } + // ], + // result: `{"count":42,"price":99.99}` + // }, + // // 布尔值处理 + // { + // body: `{"isActive":{{isActive}},"hasData":{{hasData}}}`, + // variables: [ + // { key: '{{isActive}}', value: 'true' }, + // { key: '{{hasData}}', value: 'false' } + // ], + // result: `{"isActive":true,"hasData":false}` + // }, + // // 对象类型处理 + // { + // body: `{"user":{{user}},"user2":"{{user2}}"}`, + // variables: [ + // { key: '{{user}}', value: `{"id":1,"name":"张三"}` }, + // { key: '{{user2}}', value: `{"id":1,"name":"张三"}` } + // ], + // result: `{"user":{"id":1,"name":"张三"},"user2":"{\\"id\\":1,\\"name\\":\\"张三\\"}"}` + // }, + // // 数组类型处理 + // { + // body: `{"items":{{items}}}`, + // variables: [{ key: '{{items}}', value: '[1, 2, 3]' }], + // result: `{"items":[1,2,3]}` + // }, + // // null 和 undefined 处理 + // { + // body: `{"nullValue":{{nullValue}},"undefinedValue":{{undefinedValue}}}`, + // variables: [ + // { key: '{{nullValue}}', value: 'null' }, + // { key: '{{undefinedValue}}', value: 'undefined' } + // ], + // result: `{"nullValue":null,"undefinedValue":null}` + // }, + // // 嵌套JSON结构 + // { + // body: `{"data":{"nested":{"value":"{{nestedValue}}"}}}`, + // variables: [{ key: '{{nestedValue}}', value: '嵌套值' }], + // result: `{"data":{"nested":{"value":"嵌套值"}}}` + // }, + // // 多变量替换 + // { + // body: `{"first":"{{first}}","second":"{{second}}","third":{{third}}}`, + // variables: [ + // { key: '{{first}}', value: '第一' }, + // { key: '{{second}}', value: '第二' }, + // { key: '{{third}}', value: '3' } + // ], + // result: `{"first":"第一","second":"第二","third":3}` + // }, + // // JSON字符串作为变量值 + // { + // body: `{"config":{{config}}}`, + // variables: [{ key: '{{config}}', value: '{"setting":"enabled","mode":"advanced"}' }], + // result: `{"config":{"setting":"enabled","mode":"advanced"}}` + // } + // ]; + + // for (let i = 0; i < testData.length; i++) { + // const item = testData[i]; + // let bodyStr = item.body; + // for (const variable of item.variables) { + // const isQuote = isVariableInQuotes(bodyStr, variable.key); + // bodyStr = bodyStr.replace(variable.key, valToStr(variable.value, isQuote)); + // } + // bodyStr = bodyStr.replace(/(".*?")\s*:\s*undefined\b/g, '$1:null'); + + // console.log(bodyStr === item.result, i); + // if (bodyStr !== item.result) { + // console.log(bodyStr); + // console.log(item.result); + // } else { + // try { + // JSON.parse(item.result); + // } catch (error) { + // console.log('反序列化异常', i, item.result); + // } + // } + // } + // }; + // bodyTest(); // 1. Replace {{key.key}} variables const regex1 = /\{\{\$([^.]+)\.([^$]+)\$\}\}/g; @@ -148,6 +266,10 @@ export const dispatchHttp468Request = async (props: HttpRequestProps): Promise { const nodeId = match[1]; const id = match[2]; + const fullMatch = match[0]; + + // 检查变量是否在引号内 + const isInQuotes = isVariableInQuotes(text, fullMatch); const variableVal = (() => { if (nodeId === VARIABLE_NODE_ID) { @@ -165,9 +287,9 @@ export const dispatchHttp468Request = async (props: HttpRequestProps): Promise formatVal); }); @@ -176,10 +298,16 @@ export const dispatchHttp468Request = async (props: HttpRequestProps): Promise match.slice(2, -2)))]; for (const key of uniqueKeys2) { - text = text.replace(new RegExp(`{{(${key})}}`, 'g'), () => valToStr(allVariables[key])); + const fullMatch = `{{${key}}}`; + // 检查变量是否在引号内 + const isInQuotes = isVariableInQuotes(text, fullMatch); + + text = text.replace(new RegExp(`{{(${key})}}`, ''), () => + valToStr(allVariables[key], isInQuotes) + ); } - return text.replace(/(".*?")\s*:\s*undefined\b/g, '$1: null'); + return text.replace(/(".*?")\s*:\s*undefined\b/g, '$1:null'); }; httpReqUrl = replaceStringVariables(httpReqUrl); diff --git a/packages/service/core/workflow/dispatch/tools/readFiles.ts b/packages/service/core/workflow/dispatch/tools/readFiles.ts index 1e2c5953b1d5..f4593c37557e 100644 --- a/packages/service/core/workflow/dispatch/tools/readFiles.ts +++ b/packages/service/core/workflow/dispatch/tools/readFiles.ts @@ -45,13 +45,14 @@ ${content.slice(0, 100)}${content.length > 100 ? '......' : ''} export const dispatchReadFiles = async (props: Props): Promise => { const { requestOrigin, - runningAppInfo: { teamId }, + runningUserInfo: { teamId, tmbId }, histories, chatConfig, node: { version }, params: { fileUrlList = [] } } = props; const maxFiles = chatConfig?.fileSelectConfig?.maxFiles || 20; + const customPdfParse = chatConfig?.fileSelectConfig?.customPdfParse || false; // Get files from histories const filesFromHistories = version !== '489' ? [] : getHistoryFileLinks(histories); @@ -61,7 +62,9 @@ export const dispatchReadFiles = async (props: Props): Promise => { urls: [...fileUrlList, ...filesFromHistories], requestOrigin, maxFiles, - teamId + teamId, + tmbId, + customPdfParse }); return { @@ -105,12 +108,16 @@ export const getFileContentFromLinks = async ({ urls, requestOrigin, maxFiles, - teamId + teamId, + tmbId, + customPdfParse }: { urls: string[]; requestOrigin?: string; maxFiles: number; teamId: string; + tmbId: string; + customPdfParse?: boolean; }) => { const parseUrlList = urls // Remove invalid urls @@ -205,8 +212,10 @@ export const getFileContentFromLinks = async ({ extension, isQAImport: false, teamId, + tmbId, buffer, - encoding + encoding, + customPdfParse }); // Add to buffer diff --git a/packages/service/package.json b/packages/service/package.json index 3555248b3777..c83d626c672b 100644 --- a/packages/service/package.json +++ b/packages/service/package.json @@ -6,7 +6,7 @@ "@node-rs/jieba": "1.10.0", "@xmldom/xmldom": "^0.8.10", "@zilliz/milvus2-sdk-node": "2.4.2", - "axios": "^1.5.1", + "axios": "^1.8.2", "chalk": "^5.3.0", "cheerio": "1.0.0-rc.12", "cookie": "^0.5.0", @@ -20,13 +20,13 @@ "iconv-lite": "^0.6.3", "joplin-turndown-plugin-gfm": "^1.0.12", "json5": "^2.2.3", - "jsonpath-plus": "^10.1.0", + "jsonpath-plus": "^10.3.0", "jsonwebtoken": "^9.0.2", "lodash": "^4.17.21", "mammoth": "^1.6.0", - "mongoose": "^7.0.2", + "mongoose": "^8.10.1", "multer": "1.4.5-lts.1", - "next": "14.2.5", + "next": "14.2.21", "nextjs-cors": "^2.2.0", "node-cron": "^3.0.3", "node-xlsx": "^0.24.0", diff --git a/packages/service/support/permission/controller.ts b/packages/service/support/permission/controller.ts index 19b7058f6cad..79aa9fd238e3 100644 --- a/packages/service/support/permission/controller.ts +++ b/packages/service/support/permission/controller.ts @@ -178,7 +178,7 @@ export const getClbsAndGroupsWithInfo = async ({ ]); export const delResourcePermissionById = (id: string) => { - return MongoResourcePermission.findByIdAndRemove(id); + return MongoResourcePermission.findByIdAndDelete(id); }; export const delResourcePermission = ({ session, diff --git a/packages/service/support/permission/inheritPermission.ts b/packages/service/support/permission/inheritPermission.ts index 4f9993f5a9f3..76de2b78dd91 100644 --- a/packages/service/support/permission/inheritPermission.ts +++ b/packages/service/support/permission/inheritPermission.ts @@ -196,7 +196,8 @@ export async function syncCollaborators({ permission: item.permission })), { - session + session, + ordered: true } ); } diff --git a/packages/service/support/permission/publish/authLink.ts b/packages/service/support/permission/publish/authLink.ts index 7e9c4f94d115..a3f9511af475 100644 --- a/packages/service/support/permission/publish/authLink.ts +++ b/packages/service/support/permission/publish/authLink.ts @@ -54,7 +54,7 @@ export async function authOutLinkCrud({ } /* outLink exist and it app exist */ -export async function authOutLinkValid({ +export async function authOutLinkValid({ shareId }: { shareId?: string; @@ -62,7 +62,7 @@ export async function authOutLinkValid({ if (!shareId) { return Promise.reject(OutLinkErrEnum.linkUnInvalid); } - const outLinkConfig = (await MongoOutLink.findOne({ shareId }).lean()) as OutLinkSchema; + const outLinkConfig = await MongoOutLink.findOne({ shareId }).lean>(); if (!outLinkConfig) { return Promise.reject(OutLinkErrEnum.linkUnInvalid); @@ -70,6 +70,6 @@ export async function authOutLinkValid({ return { appId: outLinkConfig.appId, - outLinkConfig + outLinkConfig: outLinkConfig }; } diff --git a/packages/service/support/permission/teamLimit.ts b/packages/service/support/permission/teamLimit.ts index 1a383f85b894..d53e016e079e 100644 --- a/packages/service/support/permission/teamLimit.ts +++ b/packages/service/support/permission/teamLimit.ts @@ -64,7 +64,7 @@ export const checkTeamDatasetLimit = async (teamId: string) => { export const checkTeamAppLimit = async (teamId: string, amount = 1) => { const [{ standardConstants }, appCount] = await Promise.all([ getTeamStandPlan({ teamId }), - MongoApp.count({ + MongoApp.countDocuments({ teamId, type: { $in: [AppTypeEnum.simple, AppTypeEnum.workflow, AppTypeEnum.plugin] } }) diff --git a/packages/service/support/user/team/controller.ts b/packages/service/support/user/team/controller.ts index d48d9bf0b2b5..d4d8a3aff958 100644 --- a/packages/service/support/user/team/controller.ts +++ b/packages/service/support/user/team/controller.ts @@ -15,7 +15,7 @@ import { TeamDefaultPermissionVal } from '@fastgpt/global/support/permission/use import { MongoMemberGroupModel } from '../../permission/memberGroup/memberGroupSchema'; import { mongoSessionRun } from '../../../common/mongo/sessionRun'; import { DefaultGroupName } from '@fastgpt/global/support/user/team/group/constant'; -import { getAIApi, openaiBaseUrl } from '../../../core/ai/config'; +import { getAIApi } from '../../../core/ai/config'; import { createRootOrg } from '../../permission/org/controllers'; import { refreshSourceAvatar } from '../../../common/file/image/controller'; @@ -43,7 +43,6 @@ async function getTeamMember(match: Record): Promise { const [{ _id }] = await MongoUsage.create( @@ -136,32 +138,79 @@ export const createTrainingUsage = async ({ source: billSource, totalPoints: 0, list: [ - { - moduleName: i18nT('common:support.wallet.moduleName.index'), - model: vectorModel, - amount: 0, - inputTokens: 0, - outputTokens: 0 - }, - { - moduleName: i18nT('common:support.wallet.moduleName.qa'), - model: agentModel, - amount: 0, - inputTokens: 0, - outputTokens: 0 - }, - { - moduleName: i18nT('common:core.dataset.training.Auto mode'), - model: agentModel, - amount: 0, - inputTokens: 0, - outputTokens: 0 - } + ...(vectorModel + ? [ + { + moduleName: i18nT('account_usage:embedding_index'), + model: vectorModel, + amount: 0, + inputTokens: 0, + outputTokens: 0 + } + ] + : []), + ...(agentModel + ? [ + { + moduleName: i18nT('account_usage:qa'), + model: agentModel, + amount: 0, + inputTokens: 0, + outputTokens: 0 + }, + { + moduleName: i18nT('account_usage:auto_index'), + model: agentModel, + amount: 0, + inputTokens: 0, + outputTokens: 0 + } + ] + : []), + ...(vllmModel + ? [ + { + moduleName: i18nT('account_usage:image_parse'), + model: vllmModel, + amount: 0, + inputTokens: 0, + outputTokens: 0 + } + ] + : []) ] } ], - { session } + { session, ordered: true } ); return { billId: String(_id) }; }; + +export const createPdfParseUsage = async ({ + teamId, + tmbId, + pages +}: { + teamId: string; + tmbId: string; + pages: number; +}) => { + const unitPrice = global.systemEnv?.customPdfParse?.price || 0; + const totalPoints = pages * unitPrice; + + createUsage({ + teamId, + tmbId, + appName: i18nT('account_usage:pdf_enhanced_parse'), + totalPoints, + source: UsageSourceEnum.pdfParse, + list: [ + { + moduleName: i18nT('account_usage:pdf_enhanced_parse'), + amount: totalPoints, + pages + } + ] + }); +}; diff --git a/packages/service/worker/htmlStr2Md/utils.ts b/packages/service/worker/htmlStr2Md/utils.ts index 8384d005a132..0602fc818b9a 100644 --- a/packages/service/worker/htmlStr2Md/utils.ts +++ b/packages/service/worker/htmlStr2Md/utils.ts @@ -1,6 +1,6 @@ import TurndownService from 'turndown'; import { ImageType } from '../readFile/type'; -import { matchMdImgTextAndUpload } from '@fastgpt/global/common/string/markdown'; +import { matchMdImg } from '@fastgpt/global/common/string/markdown'; import { getNanoid } from '@fastgpt/global/common/string/tools'; // @ts-ignore const turndownPluginGfm = require('joplin-turndown-plugin-gfm'); @@ -46,7 +46,7 @@ export const html2md = ( // Base64 img to id, otherwise it will occupy memory when going to md const { processedHtml, images } = processBase64Images(html); const md = turndownService.turndown(processedHtml); - const { text, imageList } = matchMdImgTextAndUpload(md); + const { text, imageList } = matchMdImg(md); return { rawText: text, diff --git a/packages/service/worker/readFile/index.ts b/packages/service/worker/readFile/index.ts index 1c44852a893f..45092ed72df3 100644 --- a/packages/service/worker/readFile/index.ts +++ b/packages/service/worker/readFile/index.ts @@ -9,7 +9,7 @@ import { readXlsxRawText } from './extension/xlsx'; import { readCsvRawText } from './extension/csv'; parentPort?.on('message', async (props: ReadRawTextProps) => { - const readRawContentByFileBuffer = async (params: ReadRawTextByBuffer) => { + const read = async (params: ReadRawTextByBuffer) => { switch (params.extension) { case 'txt': case 'md': @@ -27,7 +27,9 @@ parentPort?.on('message', async (props: ReadRawTextProps) => { case 'csv': return readCsvRawText(params); default: - return Promise.reject('Only support .txt, .md, .html, .pdf, .docx, pptx, .csv, .xlsx'); + return Promise.reject( + `Only support .txt, .md, .html, .pdf, .docx, pptx, .csv, .xlsx. "${params.extension}" is not supported.` + ); } }; @@ -41,7 +43,7 @@ parentPort?.on('message', async (props: ReadRawTextProps) => { try { parentPort?.postMessage({ type: 'success', - data: await readRawContentByFileBuffer(newProps) + data: await read(newProps) }); } catch (error) { console.log(error); diff --git a/packages/service/worker/readFile/parseOffice.ts b/packages/service/worker/readFile/parseOffice.ts index 177f5e2cdeeb..d4de58f2659b 100644 --- a/packages/service/worker/readFile/parseOffice.ts +++ b/packages/service/worker/readFile/parseOffice.ts @@ -45,11 +45,11 @@ const parsePowerPoint = async ({ // Returning an array of all the xml contents read using fs.readFileSync const xmlContentArray = await Promise.all( - files.map((file) => { + files.map(async (file) => { try { - return fs.promises.readFile(`${decompressPath}/${file.path}`, encoding); + return await fs.promises.readFile(`${decompressPath}/${file.path}`, encoding); } catch (err) { - return fs.promises.readFile(`${decompressPath}/${file.path}`, 'utf-8'); + return await fs.promises.readFile(`${decompressPath}/${file.path}`, 'utf-8'); } }) ); diff --git a/packages/web/components/common/DateRangePicker/index.tsx b/packages/web/components/common/DateRangePicker/index.tsx index b033cfab8628..f62766ddee7d 100644 --- a/packages/web/components/common/DateRangePicker/index.tsx +++ b/packages/web/components/common/DateRangePicker/index.tsx @@ -100,6 +100,13 @@ const DateRangePicker = ({ if (date?.to === undefined) { date.to = date.from; } + + if (date?.from) { + date.from = new Date(date.from.setHours(0, 0, 0, 0)); + } + if (date?.to) { + date.to = new Date(date.to.setHours(23, 59, 59, 999)); + } setRange(date); onChange?.(date); }} diff --git a/packages/web/components/common/Icon/constants.ts b/packages/web/components/common/Icon/constants.ts index b40c72eb1ccc..3d1d713b963f 100644 --- a/packages/web/components/common/Icon/constants.ts +++ b/packages/web/components/common/Icon/constants.ts @@ -1,4 +1,5 @@ // @ts-nocheck + export const iconPaths = { book: () => import('./icons/book.svg'), change: () => import('./icons/change.svg'), @@ -32,8 +33,10 @@ export const iconPaths = { 'common/customTitleLight': () => import('./icons/common/customTitleLight.svg'), 'common/data': () => import('./icons/common/data.svg'), 'common/dingtalkFill': () => import('./icons/common/dingtalkFill.svg'), + 'common/disable': () => import('./icons/common/disable.svg'), 'common/downArrowFill': () => import('./icons/common/downArrowFill.svg'), 'common/editor/resizer': () => import('./icons/common/editor/resizer.svg'), + 'common/enable': () => import('./icons/common/enable.svg'), 'common/errorFill': () => import('./icons/common/errorFill.svg'), 'common/file/move': () => import('./icons/common/file/move.svg'), 'common/folderFill': () => import('./icons/common/folderFill.svg'), diff --git a/packages/web/components/common/Icon/icons/common/disable.svg b/packages/web/components/common/Icon/icons/common/disable.svg new file mode 100644 index 000000000000..7d226c71cce6 --- /dev/null +++ b/packages/web/components/common/Icon/icons/common/disable.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/web/components/common/Icon/icons/common/enable.svg b/packages/web/components/common/Icon/icons/common/enable.svg new file mode 100644 index 000000000000..d381622de127 --- /dev/null +++ b/packages/web/components/common/Icon/icons/common/enable.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/web/components/common/Icon/index.tsx b/packages/web/components/common/Icon/index.tsx index aedc7ef14e60..663826591345 100644 --- a/packages/web/components/common/Icon/index.tsx +++ b/packages/web/components/common/Icon/index.tsx @@ -1,4 +1,4 @@ -import React, { useEffect } from 'react'; +import React, { useEffect, useState } from 'react'; import type { IconProps } from '@chakra-ui/react'; import { Box, Icon } from '@chakra-ui/react'; import { iconPaths } from './constants'; @@ -8,7 +8,7 @@ import { useRefresh } from '../../../hooks/useRefresh'; const iconCache: Record = {}; const MyIcon = ({ name, w = 'auto', h = 'auto', ...props }: { name: IconNameType } & IconProps) => { - const { refresh } = useRefresh(); + const [, setUpdate] = useState(0); useEffect(() => { if (iconCache[name]) { @@ -20,7 +20,7 @@ const MyIcon = ({ name, w = 'auto', h = 'auto', ...props }: { name: IconNameType const component = { as: icon.default }; // Store in cache iconCache[name] = component; - refresh(); + setUpdate((prev) => prev + 1); // force update }) .catch((error) => console.log(error)); }, [name]); diff --git a/packages/web/components/common/Image/PhotoView.tsx b/packages/web/components/common/Image/PhotoView.tsx index e7867e212d44..a0a8a8aa90e3 100644 --- a/packages/web/components/common/Image/PhotoView.tsx +++ b/packages/web/components/common/Image/PhotoView.tsx @@ -17,7 +17,7 @@ const MyPhotoView = (props: ImageProps) => { loadingElement={} > - + ); diff --git a/packages/web/components/common/Input/NumberInput/index.tsx b/packages/web/components/common/Input/NumberInput/index.tsx index 3f87817bdd99..fb817d8e5314 100644 --- a/packages/web/components/common/Input/NumberInput/index.tsx +++ b/packages/web/components/common/Input/NumberInput/index.tsx @@ -10,8 +10,9 @@ import React from 'react'; import MyIcon from '../../Icon'; import { UseFormRegister } from 'react-hook-form'; -type Props = Omit & { +type Props = Omit & { onChange?: (e?: number) => any; + onBlur?: (e?: number) => any; placeholder?: string; register?: UseFormRegister; name?: string; @@ -19,11 +20,21 @@ type Props = Omit & { }; const MyNumberInput = (props: Props) => { - const { register, name, onChange, placeholder, bg, ...restProps } = props; + const { register, name, onChange, onBlur, placeholder, bg, ...restProps } = props; return ( { + if (!onBlur) return; + const numE = Number(e.target.value); + if (isNaN(numE)) { + // @ts-ignore + onBlur(''); + } else { + onBlur(numE); + } + }} onChange={(e) => { if (!onChange) return; const numE = Number(e); @@ -38,6 +49,8 @@ const MyNumberInput = (props: Props) => { { return ( - {isLoading && } {children} + {isLoading && } ); }; diff --git a/packages/web/components/common/MySelect/MultipleSelect.tsx b/packages/web/components/common/MySelect/MultipleSelect.tsx index 1435babcfa48..cdf45177244b 100644 --- a/packages/web/components/common/MySelect/MultipleSelect.tsx +++ b/packages/web/components/common/MySelect/MultipleSelect.tsx @@ -98,7 +98,6 @@ const MultipleSelect = ({ return ( ({ whiteSpace={'pre-wrap'} fontSize={'sm'} gap={2} + {...menuItemStyles} > {item.icon && } @@ -204,6 +204,7 @@ const MultipleSelect = ({ }} onClick={(e) => { e.stopPropagation(); + e.preventDefault(); onclickItem(item.value); }} /> @@ -230,7 +231,6 @@ const MultipleSelect = ({ overflowY={'auto'} > { e.stopPropagation(); @@ -241,6 +241,7 @@ const MultipleSelect = ({ fontSize={'sm'} gap={2} mb={1} + {...menuItemStyles} > {t('common:common.All')} diff --git a/packages/web/components/common/MySelect/index.tsx b/packages/web/components/common/MySelect/index.tsx index c9eb6230dc45..d6055b7c122c 100644 --- a/packages/web/components/common/MySelect/index.tsx +++ b/packages/web/components/common/MySelect/index.tsx @@ -4,7 +4,8 @@ import React, { useMemo, useEffect, useImperativeHandle, - ForwardedRef + ForwardedRef, + useState } from 'react'; import { Menu, @@ -15,7 +16,8 @@ import { MenuButton, Box, css, - Flex + Flex, + Input } from '@chakra-ui/react'; import type { ButtonProps, MenuItemProps } from '@chakra-ui/react'; import MyIcon from '../Icon'; @@ -33,8 +35,10 @@ import { useScrollPagination } from '../../../hooks/useScrollPagination'; export type SelectProps = ButtonProps & { value?: T; placeholder?: string; + isSearch?: boolean; list: { alias?: string; + icon?: string; label: string | React.ReactNode; description?: string; value: T; @@ -49,6 +53,7 @@ const MySelect = ( { placeholder, value, + isSearch = false, width = '100%', list = [], onchange, @@ -63,6 +68,7 @@ const MySelect = ( const ButtonRef = useRef(null); const MenuListRef = useRef(null); const SelectedItemRef = useRef(null); + const SearchInputRef = useRef(null); const menuItemStyles: MenuItemProps = { borderRadius: 'sm', @@ -79,6 +85,18 @@ const MySelect = ( const { isOpen, onOpen, onClose } = useDisclosure(); const selectItem = useMemo(() => list.find((item) => item.value === value), [list, value]); + const [search, setSearch] = useState(''); + const filterList = useMemo(() => { + if (!isSearch || !search) { + return list; + } + return list.filter((item) => { + const text = `${item.label?.toString()}${item.alias}${item.value}`; + const regx = new RegExp(search, 'gi'); + return regx.test(text); + }); + }, [list, search, isSearch]); + useImperativeHandle(ref, () => ({ focus() { onOpen(); @@ -90,17 +108,19 @@ const MySelect = ( const menu = MenuListRef.current; const selectedItem = SelectedItemRef.current; menu.scrollTop = selectedItem.offsetTop - menu.offsetTop - 100; + + if (isSearch) { + setSearch(''); + } } - }, [isOpen]); + }, [isSearch, isOpen]); const { runAsync: onChange, loading } = useRequest2((val: T) => onchange?.(val)); - const isSelecting = loading || isLoading; - const ListRender = useMemo(() => { return ( <> - {list.map((item, i) => ( + {filterList.map((item, i) => ( ( fontSize={'sm'} display={'block'} > - {item.label} + + {item.icon && } + {item.label} + {item.description && ( {item.description} @@ -135,7 +158,9 @@ const MySelect = ( ))} ); - }, [list, value]); + }, [filterList, value]); + + const isSelecting = loading || isLoading; return ( ( {...props} > - {isSelecting && } - {selectItem?.alias || selectItem?.label || placeholder} + {isSelecting && } + {isSearch && isOpen ? ( + setSearch(e.target.value)} + placeholder={ + selectItem?.alias || + (typeof selectItem?.label === 'string' ? selectItem?.label : placeholder) + } + size={'sm'} + w={'100%'} + color={'myGray.700'} + onBlur={() => { + setTimeout(() => { + SearchInputRef?.current?.focus(); + }, 0); + }} + /> + ) : ( + <> + {selectItem?.icon && } + {selectItem?.alias || selectItem?.label || placeholder} + + )} diff --git a/packages/web/components/common/Radio/LeftRadio.tsx b/packages/web/components/common/Radio/LeftRadio.tsx index 1d7492120ca2..9e244ba4ce71 100644 --- a/packages/web/components/common/Radio/LeftRadio.tsx +++ b/packages/web/components/common/Radio/LeftRadio.tsx @@ -1,26 +1,24 @@ import React from 'react'; import { Box, Flex, useTheme, Grid, type GridProps, HStack } from '@chakra-ui/react'; import { useTranslation } from 'next-i18next'; -import MyTooltip from '../MyTooltip'; import QuestionTip from '../MyTooltip/QuestionTip'; -// @ts-ignore -interface Props extends GridProps { +type Props = Omit & { list: { title: string; desc?: string; - value: any; + value: T; children?: React.ReactNode; tooltip?: string; }[]; align?: 'flex-top' | 'center'; - value: any; + value: T; defaultBg?: string; activeBg?: string; - onChange: (e: any) => void; -} + onChange: (e: T) => void; +}; -const LeftRadio = ({ +const LeftRadio = ({ list, value, align = 'flex-top', @@ -30,7 +28,7 @@ const LeftRadio = ({ activeBg = 'primary.50', onChange, ...props -}: Props) => { +}: Props) => { const { t } = useTranslation(); const theme = useTheme(); @@ -39,7 +37,7 @@ const LeftRadio = ({ {list.map((item) => ( {typeof item.title === 'string' ? t(item.title as any) : item.title} - {!!item.tooltip && } + {!!item.tooltip && } {!!item.desc && ( diff --git a/packages/web/hooks/useScrollPagination.tsx b/packages/web/hooks/useScrollPagination.tsx index a326a20b7499..9b90f5eebf6d 100644 --- a/packages/web/hooks/useScrollPagination.tsx +++ b/packages/web/hooks/useScrollPagination.tsx @@ -214,6 +214,11 @@ export function useScrollPagination< async (init = false, ScrollContainerRef?: RefObject) => { if (noMore && !init) return; + if (init) { + setData([]); + setTotal(0); + } + const offset = init ? 0 : data.length; setTrue(); @@ -288,7 +293,7 @@ export function useScrollPagination< // Watch scroll position useThrottleEffect( () => { - if (!ref?.current || noMore) return; + if (!ref?.current || noMore || isLoading || data.length === 0) return; const { scrollTop, scrollHeight, clientHeight } = ref.current; if ( diff --git a/packages/web/i18n/en/account_model.json b/packages/web/i18n/en/account_model.json new file mode 100644 index 000000000000..9fd0f49765bd --- /dev/null +++ b/packages/web/i18n/en/account_model.json @@ -0,0 +1,51 @@ +{ + "api_key": "API key", + "azure": "Azure", + "base_url": "Base url", + "channel_name": "Channel", + "channel_priority": "Priority", + "channel_priority_tip": "The higher the priority channel, the easier it is to be requested", + "channel_status": "state", + "channel_status_auto_disabled": "Automatically disable", + "channel_status_disabled": "Disabled", + "channel_status_enabled": "Enable", + "channel_status_unknown": "unknown", + "channel_type": "Manufacturer", + "clear_model": "Clear the model", + "copy_model_id_success": "Copyed model id", + "create_channel": "Added channels", + "default_url": "Default address", + "detail": "Detail", + "duration": "Duration", + "edit": "edit", + "edit_channel": "Channel configuration", + "enable_channel": "Enable", + "forbid_channel": "Disabled", + "maxToken_tip": "The model max_tokens parameter, if left blank, means that the model does not support it.", + "key_type": "API key format:", + "log": "Call log", + "log_detail": "Log details", + "log_request_id_search": "Search by requestId", + "log_status": "Status", + "mapping": "Model Mapping", + "mapping_tip": "A valid Json is required. \nThe model can be mapped when sending a request to the actual address. \nFor example:\n{\n \n \"gpt-4o\": \"gpt-4o-test\"\n\n}\n\nWhen FastGPT requests the gpt-4o model, the gpt-4o-test model is sent to the actual address, instead of gpt-4o.", + "max_temperature_tip": "If the model temperature parameter is not filled in, it means that the model does not support the temperature parameter.", + "model": "Model", + "model_name": "Model name", + "model_test": "Model testing", + "model_tokens": "Input/Output tokens", + "request_at": "Request time", + "request_duration": "Request duration: {{duration}}s", + "running_test": "In testing", + "search_model": "Search for models", + "select_channel": "Select a channel name", + "select_model": "Select a model", + "select_model_placeholder": "Select the model available under this channel", + "select_provider_placeholder": "Search for manufacturers", + "selected_model_empty": "Choose at least one model", + "start_test": "Start testing {{num}} models", + "test_failed": "There are {{num}} models that report errors", + "vlm_model": "Vlm", + "vlm_model_tip": "Used to generate additional indexing of images in a document in the knowledge base", + "waiting_test": "Waiting for testing" +} diff --git a/packages/web/i18n/en/account_usage.json b/packages/web/i18n/en/account_usage.json index bfb9cd72e025..459007293349 100644 --- a/packages/web/i18n/en/account_usage.json +++ b/packages/web/i18n/en/account_usage.json @@ -2,6 +2,7 @@ "ai_model": "AI model", "all": "all", "app_name": "Application name", + "auto_index": "Auto index", "billing_module": "Deduction module", "confirm_export": "A total of {{total}} pieces of data were filtered out. Are you sure to export?", "current_filter_conditions": "Current filter conditions", @@ -9,6 +10,7 @@ "details": "Details", "dingtalk": "DingTalk", "duration_seconds": "Duration (seconds)", + "embedding_index": "Embedding", "every_day": "Day", "every_month": "Moon", "export_confirm": "Export confirmation", @@ -16,6 +18,7 @@ "export_title": "Time,Members,Type,Project name,AI points", "feishu": "Feishu", "generation_time": "Generation time", + "image_parse": "Image tagging", "input_token_length": "input tokens", "member": "member", "member_name": "Member name", @@ -25,8 +28,12 @@ "official_account": "Official Account", "order_number": "Order number", "output_token_length": "output tokens", + "pages": "Pages", + "pdf_enhanced_parse": "PDF Enhanced Analysis", + "pdf_parse": "PDF Analysis", "points": "Points", "project_name": "Project name", + "qa": "QA", "select_member_and_source_first": "Please select members and types first", "share": "Share Link", "source": "source", diff --git a/packages/web/i18n/en/app.json b/packages/web/i18n/en/app.json index 0295f3f94616..cc0bc8f4602b 100644 --- a/packages/web/i18n/en/app.json +++ b/packages/web/i18n/en/app.json @@ -105,6 +105,9 @@ "open_vision_function_tip": "Models with icon switches have image recognition capabilities. \nAfter being turned on, the model will parse the pictures in the file link and automatically parse the pictures in the user's question (user question ≤ 500 words).", "or_drag_JSON": "or drag in JSON file", "paste_config_or_drag": "Paste config or drag JSON file here", + "pdf_enhance_parse": "PDF enhancement analysis", + "pdf_enhance_parse_price": "{{price}}Points/page", + "pdf_enhance_parse_tips": "Calling PDF recognition model for parsing, you can convert it into Markdown and retain pictures in the document. At the same time, you can also identify scanned documents, which will take a long time to identify them.", "permission.des.manage": "Based on write permissions, you can configure publishing channels, view conversation logs, and assign permissions to the application.", "permission.des.read": "Use the app to have conversations", "permission.des.write": "Can view and edit apps", diff --git a/packages/web/i18n/en/common.json b/packages/web/i18n/en/common.json index d1b1b39be191..d71060c08059 100644 --- a/packages/web/i18n/en/common.json +++ b/packages/web/i18n/en/common.json @@ -125,7 +125,6 @@ "common.Copy Successful": "Copied Successfully", "common.Copy_failed": "Copy Failed, Please Copy Manually", "common.Create Failed": "Creation Failed", - "common.Create New": "Create", "common.Create Success": "Created Successfully", "common.Create Time": "Creation Time", "common.Creating": "Creating", @@ -563,10 +562,7 @@ "core.dataset.file": "File", "core.dataset.folder": "Directory", "core.dataset.import.Auto mode Estimated Price Tips": "Requires calling the file processing model, which consumes a lot of tokens: {{price}} points/1K tokens", - "core.dataset.import.Auto process": "Automatic", - "core.dataset.import.Auto process desc": "Automatically set segmentation and preprocessing rules", "core.dataset.import.Chunk Range": "Range: {{min}}~{{max}}", - "core.dataset.import.Chunk Split": "Chunks", "core.dataset.import.Chunk Split Tip": "Segment the text according to certain rules and convert it into a format that can be semantically searched. Suitable for most scenarios. No additional model processing is required, and the cost is low.", "core.dataset.import.Continue upload": "Continue upload", "core.dataset.import.Custom process": "Custom Rules", @@ -576,7 +572,6 @@ "core.dataset.import.Custom split char Tips": "Allows you to segment based on custom separators. Usually used for pre-processed data, using specific separators for precise segmentation.", "core.dataset.import.Custom text": "Custom Text", "core.dataset.import.Custom text desc": "Manually enter a piece of text as a dataset", - "core.dataset.import.Data Preprocessing": "Data Processing", "core.dataset.import.Data process params": "Data Processing Parameters", "core.dataset.import.Down load csv template": "Click to Download CSV Template", "core.dataset.import.Embedding Estimated Price Tips": "Only use the index model, consuming a small amount of AI points: {{price}} points/1K tokens", @@ -598,7 +593,6 @@ "core.dataset.import.Source name": "Source Name", "core.dataset.import.Sources list": "Sources", "core.dataset.import.Start upload": "Start Upload", - "core.dataset.import.Total files": "Total {{total}} Files", "core.dataset.import.Upload complete": "Upload complete", "core.dataset.import.Upload data": "Confirm Upload", "core.dataset.import.Upload file progress": "File Upload Progress", @@ -650,10 +644,10 @@ "core.dataset.training.Agent queue": "QA Training Queue", "core.dataset.training.Auto mode": "Auto index", "core.dataset.training.Auto mode Tip": "Increase the semantic richness of data blocks by generating related questions and summaries through sub-indexes and calling models, making it more conducive to retrieval. Requires more storage space and increases AI call times.", - "core.dataset.training.Chunk mode": "Default", + "core.dataset.training.Chunk mode": "Chunk", "core.dataset.training.Full": "Estimated Over 5 Minutes", "core.dataset.training.Leisure": "Idle", - "core.dataset.training.QA mode": "QA Chunks", + "core.dataset.training.QA mode": "QA", "core.dataset.training.Vector queue": "Index Queue", "core.dataset.training.Waiting": "Estimated 5 Minutes", "core.dataset.training.Website Sync": "Website Sync", @@ -862,7 +856,6 @@ "dataset.collections.Select Collection": "Select File", "dataset.collections.Select One Collection To Store": "Select a File to Store", "dataset.data.Can not edit": "No Edit Permission", - "dataset.data.Custom Index Number": "Custom Index {{number}}", "dataset.data.Default Index": "Default Index", "dataset.data.Delete Tip": "Confirm to Delete This Data?", "dataset.data.Index Placeholder": "Enter Index Text Content", @@ -889,6 +882,10 @@ "error.upload_file_error_filename": "{{name}} Upload Failed", "error.upload_image_error": "File upload failed", "error.username_empty": "Account cannot be empty", + "error_collection_not_exist": "The collection does not exist", + "error_embedding_not_config": "Unconfigured index model", + "error_llm_not_config": "Unconfigured file understanding model", + "error_vlm_not_config": "Image comprehension model not configured", "extraction_results": "Extraction Results", "field_name": "Field Name", "free": "Free", @@ -956,6 +953,7 @@ "new_create": "Create New", "no": "No", "no_laf_env": "System Not Configured with Laf Environment", + "not_model_config": "No related model configured", "not_yet_introduced": "No Introduction Yet", "option": "Option", "pay.amount": "Amount", @@ -1121,7 +1119,6 @@ "support.wallet.invoice_detail": "Invoice Details", "support.wallet.invoice_info": "The invoice will be sent to the email within 3-7 working days, please wait patiently", "support.wallet.invoicing": "Invoicing", - "support.wallet.moduleName.index": "Index Generation", "support.wallet.moduleName.qa": "QA Split", "support.wallet.noBill": "No Bill Records", "support.wallet.no_invoice": "No Invoice Records", diff --git a/packages/web/i18n/en/dataset.json b/packages/web/i18n/en/dataset.json index 79ce4ac11a7a..0902a421ed41 100644 --- a/packages/web/i18n/en/dataset.json +++ b/packages/web/i18n/en/dataset.json @@ -3,11 +3,16 @@ "add_file": "Import", "api_file": "API Dataset", "api_url": "API Url", + "auto_indexes": "Automatically generate supplementary indexes", + "auto_indexes_tips": "Additional index generation is performed through large models to improve semantic richness and improve retrieval accuracy.", "chunk_max_tokens": "max_tokens", "close_auto_sync": "Are you sure you want to turn off automatic sync?", "collection.Create update time": "Creation/Update Time", "collection.Training type": "Training", + "collection.training_type": "Chunk type", "collection_data_count": "Data amount", + "collection_metadata_custom_pdf_parse": "PDF enhancement analysis", + "collection_metadata_image_parse": "Image tagging", "collection_not_support_retraining": "This collection type does not support retuning parameters", "collection_not_support_sync": "This collection does not support synchronization", "collection_sync": "Sync data", @@ -22,12 +27,21 @@ "custom_data_process_params_desc": "Customize data processing rules", "data.ideal_chunk_length": "ideal block length", "data_amount": "{{dataAmount}} Datas, {{indexAmount}} Indexes", + "data_index_custom": "Custom index", + "data_index_default": "Default index", + "data_index_image": "Image Index", + "data_index_num": "Index {{index}}", + "data_index_question": "Inferred question index", + "data_index_summary": "Summary index", "data_process_params": "Params", "data_process_setting": "Processing config", "dataset.Unsupported operation": "dataset.Unsupported operation", "dataset.no_collections": "No datasets available", "dataset.no_tags": "No tags available", + "default_params": "default", + "default_params_desc": "Use system default parameters and rules", "edit_dataset_config": "Edit knowledge base configuration", + "enhanced_indexes": "Index enhancement", "error.collectionNotFound": "Collection not found~", "external_file": "External File Library", "external_file_dataset_desc": "Import files from an external file library to build a Dataset. The files will not be stored again.", @@ -38,19 +52,38 @@ "feishu_dataset": "Feishu Dataset", "feishu_dataset_config": "Feishu Dataset Config", "feishu_dataset_desc": "Can build a dataset using Feishu documents by configuring permissions, without secondary storage", + "file_list": "File list", "file_model_function_tip": "Enhances indexing and QA generation", "filename": "Filename", "folder_dataset": "Folder", "ideal_chunk_length": "ideal block length", "ideal_chunk_length_tips": "Segment according to the end symbol and combine multiple segments into one block. This value determines the estimated size of the block, if there is any fluctuation.", + "image_auto_parse": "Automatic image indexing", + "image_auto_parse_tips": "Call VLM to automatically label the pictures in the document and generate additional search indexes", "import.Auto mode Estimated Price Tips": "The text understanding model needs to be called, which requires more points: {{price}} points/1K tokens", "import.Embedding Estimated Price Tips": "Only use the index model and consume a small amount of AI points: {{price}} points/1K tokens", + "import_confirm": "Confirm upload", + "import_data_preview": "Data preview", + "import_data_process_setting": "Data processing method settings", + "import_file_parse_setting": "File parsing settings", + "import_model_config": "Model selection", + "import_param_setting": "Parameter settings", + "import_select_file": "Select a file", "is_open_schedule": "Enable scheduled synchronization", + "keep_image": "Keep the picture", "move.hint": "After moving, the selected knowledge base/folder will inherit the permission settings of the new folder, and the original permission settings will become invalid.", "open_auto_sync": "After scheduled synchronization is turned on, the system will try to synchronize the collection from time to time every day. During the collection synchronization period, the collection data will not be searched.", + "params_setting": "Parameter settings", + "pdf_enhance_parse": "PDF enhancement analysis", + "pdf_enhance_parse_price": "{{price}} points/page", + "pdf_enhance_parse_tips": "Calling PDF recognition model for parsing, you can convert it into Markdown and retain pictures in the document. At the same time, you can also identify scanned documents, which will take a long time to identify them.", "permission.des.manage": "Can manage the entire knowledge base data and information", "permission.des.read": "View knowledge base content", "permission.des.write": "Ability to add and change knowledge base content", + "preview_chunk": "Preview chunks", + "preview_chunk_empty": "Unable to read the contents of the file", + "preview_chunk_intro": "Display up to 10 pieces", + "preview_chunk_not_selected": "Click on the file on the left to preview", "rebuild_embedding_start_tip": "Index model switching task has started", "rebuilding_index_count": "Number of indexes being rebuilt: {{count}}", "request_headers": "Request headers, will automatically append 'Bearer '", @@ -72,8 +105,10 @@ "tag.tags": "Tags", "tag.total_tags": "Total {{total}} tags", "the_knowledge_base_has_indexes_that_are_being_trained_or_being_rebuilt": "The Dataset has indexes that are being trained or rebuilt", + "total_num_files": "Total {{total}} files", "training_mode": "Chunk mode", "vector_model_max_tokens_tip": "Each chunk of data has a maximum length of 3000 tokens", + "vllm_model": "Image understanding model", "website_dataset": "Website Sync", "website_dataset_desc": "Website sync allows you to build a Dataset directly using a web link.", "yuque_dataset": "Yuque Dataset", diff --git a/packages/web/i18n/zh-CN/account.json b/packages/web/i18n/zh-CN/account.json index 4d15385151a6..f7ed408594ea 100644 --- a/packages/web/i18n/zh-CN/account.json +++ b/packages/web/i18n/zh-CN/account.json @@ -3,7 +3,7 @@ "add_default_model": "添加预设模型", "api_key": "API 密钥", "bills_and_invoices": "账单与发票", - "channel": "渠道", + "channel": "模型渠道", "config_model": "模型配置", "confirm_logout": "确认退出登录?", "create_channel": "新增渠道", diff --git a/packages/web/i18n/zh-CN/account_model.json b/packages/web/i18n/zh-CN/account_model.json new file mode 100644 index 000000000000..63792289dc8e --- /dev/null +++ b/packages/web/i18n/zh-CN/account_model.json @@ -0,0 +1,51 @@ +{ + "api_key": "API 密钥", + "azure": "微软 Azure", + "base_url": "代理地址", + "channel_name": "渠道名", + "channel_priority": "优先级", + "channel_priority_tip": "优先级越高的渠道,越容易被请求到", + "channel_status": "状态", + "channel_status_auto_disabled": "自动禁用", + "channel_status_disabled": "禁用", + "channel_status_enabled": "启用", + "channel_status_unknown": "未知", + "channel_type": "厂商", + "clear_model": "清空模型", + "copy_model_id_success": "已复制模型id", + "create_channel": "新增渠道", + "default_url": "默认地址", + "detail": "详情", + "duration": "耗时", + "edit": "编辑", + "edit_channel": "渠道配置", + "enable_channel": "启用", + "forbid_channel": "禁用", + "maxToken_tip": "模型 max_tokens 参数,如果留空,则代表模型不支持该参数。", + "key_type": "API key 格式: ", + "log": "调用日志", + "log_detail": "日志详情", + "log_request_id_search": "根据 requestId 搜索", + "log_status": "状态", + "mapping": "模型映射", + "mapping_tip": "需填写一个有效 Json。可在向实际地址发送请求时,对模型进行映射。例如:\n{\n \"gpt-4o\": \"gpt-4o-test\"\n}\n当 FastGPT 请求 gpt-4o 模型时,会向实际地址发送 gpt-4o-test 的模型,而不是 gpt-4o。", + "max_temperature_tip": "模型 temperature 参数,不填则代表模型不支持 temperature 参数。", + "model": "模型", + "model_name": "模型名", + "model_test": "模型测试", + "model_tokens": "输入/输出 Tokens", + "request_at": "请求时间", + "request_duration": "请求时长: {{duration}}s", + "running_test": "测试中", + "search_model": "搜索模型", + "select_channel": "选择渠道名", + "select_model": "选择模型", + "select_model_placeholder": "选择该渠道下可用的模型", + "select_provider_placeholder": "搜索厂商", + "selected_model_empty": "至少选择一个模型", + "start_test": "开始测试{{num}}个模型", + "test_failed": "有{{num}}个模型报错", + "vlm_model": "图片理解模型", + "vlm_model_tip": "用于知识库中对文档中的图片进行额外的索引生成", + "waiting_test": "等待测试" +} diff --git a/packages/web/i18n/zh-CN/account_usage.json b/packages/web/i18n/zh-CN/account_usage.json index 3b7bb0792516..9255688570fc 100644 --- a/packages/web/i18n/zh-CN/account_usage.json +++ b/packages/web/i18n/zh-CN/account_usage.json @@ -2,6 +2,7 @@ "ai_model": "AI 模型", "all": "所有", "app_name": "应用名", + "auto_index": "索引增强", "billing_module": "扣费模块", "confirm_export": "共筛选出 {{total}} 条数据,是否确认导出?", "current_filter_conditions": "当前筛选条件:", @@ -9,6 +10,7 @@ "details": "详情", "dingtalk": "钉钉", "duration_seconds": "时长(秒)", + "embedding_index": "索引生成", "every_day": "天", "every_month": "月", "every_week": "每周", @@ -18,6 +20,7 @@ "export_title": "时间,成员,类型,项目名,AI 积分消耗", "feishu": "飞书", "generation_time": "生成时间", + "image_parse": "图片标注", "input_token_length": "输入 tokens", "member": "成员", "member_name": "成员名", @@ -27,8 +30,12 @@ "official_account": "公众号", "order_number": "订单号", "output_token_length": "输出 tokens", + "pages": "页数", + "pdf_enhanced_parse": "PDF 增强解析", + "pdf_parse": "PDF 解析", "points": "积分", "project_name": "项目名", + "qa": "问答对提取", "select_member_and_source_first": "请先选中成员和类型", "share": "分享链接", "source": "来源", diff --git a/packages/web/i18n/zh-CN/app.json b/packages/web/i18n/zh-CN/app.json index e76d5901ff4d..75232c3d26fa 100644 --- a/packages/web/i18n/zh-CN/app.json +++ b/packages/web/i18n/zh-CN/app.json @@ -105,6 +105,9 @@ "open_vision_function_tip": "有图示开关的模型即拥有图片识别能力。若开启,模型会解析文件链接里的图片,并自动解析用户问题中的图片(用户问题≤500字时生效)。", "or_drag_JSON": "或拖入JSON文件", "paste_config_or_drag": "粘贴配置或拖入 JSON 文件", + "pdf_enhance_parse": "PDF增强解析", + "pdf_enhance_parse_price": "{{price}}积分/页", + "pdf_enhance_parse_tips": "调用 PDF 识别模型进行解析,可以将其转换成 Markdown 并保留文档中的图片,同时也可以对扫描件进行识别,识别时间较长。", "permission.des.manage": "写权限基础上,可配置发布渠道、查看对话日志、分配该应用权限", "permission.des.read": "可使用该应用进行对话", "permission.des.write": "可查看和编辑应用", diff --git a/packages/web/i18n/zh-CN/common.json b/packages/web/i18n/zh-CN/common.json index 640486d9f689..47821542d1bc 100644 --- a/packages/web/i18n/zh-CN/common.json +++ b/packages/web/i18n/zh-CN/common.json @@ -129,7 +129,6 @@ "common.Copy Successful": "复制成功", "common.Copy_failed": "复制失败,请手动复制", "common.Create Failed": "创建异常", - "common.Create New": "新建", "common.Create Success": "创建成功", "common.Create Time": "创建时间", "common.Creating": "创建中", @@ -566,10 +565,7 @@ "core.dataset.file": "文件", "core.dataset.folder": "目录", "core.dataset.import.Auto mode Estimated Price Tips": "需调用文本理解模型,需要消耗较多AI 积分:{{price}} 积分/1K tokens", - "core.dataset.import.Auto process": "自动", - "core.dataset.import.Auto process desc": "自动设置分割和预处理规则", "core.dataset.import.Chunk Range": "范围:{{min}}~{{max}}", - "core.dataset.import.Chunk Split": "直接分段", "core.dataset.import.Chunk Split Tip": "将文本按一定的规则进行分段处理后,转成可进行语义搜索的格式,适合绝大多数场景。不需要调用模型额外处理,成本低。", "core.dataset.import.Continue upload": "继续上传", "core.dataset.import.Custom process": "自定义规则", @@ -579,7 +575,6 @@ "core.dataset.import.Custom split char Tips": "允许你根据自定义的分隔符进行分块。通常用于已处理好的数据,使用特定的分隔符来精确分块。", "core.dataset.import.Custom text": "自定义文本", "core.dataset.import.Custom text desc": "手动输入一段文本作为数据集", - "core.dataset.import.Data Preprocessing": "数据处理", "core.dataset.import.Data process params": "数据处理参数", "core.dataset.import.Down load csv template": "点击下载 CSV 模板", "core.dataset.import.Embedding Estimated Price Tips": "仅使用索引模型,消耗少量 AI 积分:{{price}} 积分/1K tokens", @@ -601,7 +596,6 @@ "core.dataset.import.Source name": "来源名", "core.dataset.import.Sources list": "来源列表", "core.dataset.import.Start upload": "开始上传", - "core.dataset.import.Total files": "共 {{total}} 个文件", "core.dataset.import.Upload complete": "完成上传", "core.dataset.import.Upload data": "确认上传", "core.dataset.import.Upload file progress": "文件上传进度", @@ -651,12 +645,12 @@ "core.dataset.test.test result placeholder": "测试结果将在这里展示", "core.dataset.test.test result tip": "根据知识库内容与测试文本的相似度进行排序,你可以根据测试结果调整对应的文本。\n注意:测试记录中的数据可能已经被修改过,点击某条测试数据后将展示最新的数据。", "core.dataset.training.Agent queue": "QA 训练排队", - "core.dataset.training.Auto mode": "增强处理", + "core.dataset.training.Auto mode": "补充索引", "core.dataset.training.Auto mode Tip": "通过子索引以及调用模型生成相关问题与摘要,来增加数据块的语义丰富度,更利于检索。需要消耗更多的存储空间和增加 AI 调用次数。", - "core.dataset.training.Chunk mode": "直接分段", + "core.dataset.training.Chunk mode": "直接分块", "core.dataset.training.Full": "预计 5 分钟以上", "core.dataset.training.Leisure": "空闲", - "core.dataset.training.QA mode": "问答拆分", + "core.dataset.training.QA mode": "问答对提取", "core.dataset.training.Vector queue": "索引排队", "core.dataset.training.Waiting": "预计 5 分钟", "core.dataset.training.Website Sync": "Web 站点同步", @@ -670,6 +664,7 @@ "core.dataset.website.Selector Course": "使用教程", "core.dataset.website.Start Sync": "开始同步", "core.dataset.website.UnValid Website Tip": "您的站点可能非静态站点,无法同步", + "core.dataset.error.unExistDataset": "数据集不存在", "core.module.Add question type": "添加问题类型", "core.module.Add_option": "添加选项", "core.module.Can not connect self": "不能连接自身", @@ -865,7 +860,6 @@ "dataset.collections.Select Collection": "选择文件", "dataset.collections.Select One Collection To Store": "选择一个文件进行存储", "dataset.data.Can not edit": "无编辑权限", - "dataset.data.Custom Index Number": "自定义索引{{number}}", "dataset.data.Default Index": "默认索引", "dataset.data.Delete Tip": "确认删除该条数据?", "dataset.data.Index Placeholder": "输入索引文本内容", @@ -892,6 +886,10 @@ "error.upload_file_error_filename": "{{name}} 上传失败", "error.upload_image_error": "上传文件失败", "error.username_empty": "账号不能为空", + "error_collection_not_exist": "集合不存在", + "error_embedding_not_config": "未配置索引模型", + "error_llm_not_config": "未配置文件理解模型", + "error_vlm_not_config": "未配置图片理解模型", "extraction_results": "提取结果", "field_name": "字段名", "free": "免费", @@ -959,6 +957,7 @@ "new_create": "新建", "no": "否", "no_laf_env": "系统未配置Laf环境", + "not_model_config": "未配置相关模型", "not_yet_introduced": "暂无介绍", "option": "选项", "pay.amount": "金额", @@ -1124,7 +1123,6 @@ "support.wallet.invoice_detail": "发票详情", "support.wallet.invoice_info": "发票将在 3-7 个工作日内发送至邮箱,请耐心等待", "support.wallet.invoicing": "开票", - "support.wallet.moduleName.index": "索引生成", "support.wallet.moduleName.qa": "QA 拆分", "support.wallet.noBill": "无账单记录~", "support.wallet.no_invoice": "暂无开票记录", diff --git a/packages/web/i18n/zh-CN/dataset.json b/packages/web/i18n/zh-CN/dataset.json index 682d2ccdd849..2dc1cc54cc00 100644 --- a/packages/web/i18n/zh-CN/dataset.json +++ b/packages/web/i18n/zh-CN/dataset.json @@ -3,11 +3,16 @@ "add_file": "添加文件", "api_file": "API 文件库", "api_url": "接口地址", + "auto_indexes": "自动生成补充索引", + "auto_indexes_tips": "通过大模型进行额外索引生成,提高语义丰富度,提高检索的精度。", "chunk_max_tokens": "分块上限", "close_auto_sync": "确认关闭自动同步功能?", "collection.Create update time": "创建/更新时间", "collection.Training type": "训练模式", + "collection.training_type": "处理模式", "collection_data_count": "数据量", + "collection_metadata_custom_pdf_parse": "PDF增强解析", + "collection_metadata_image_parse": "图片标注", "collection_not_support_retraining": "该集合类型不支持重新调整参数", "collection_not_support_sync": "该集合不支持同步", "collection_sync": "立即同步", @@ -22,12 +27,21 @@ "custom_data_process_params_desc": "自定义设置数据处理规则", "data.ideal_chunk_length": "理想分块长度", "data_amount": "{{dataAmount}} 组数据, {{indexAmount}} 组索引", + "data_index_custom": "自定义索引", + "data_index_default": "默认索引", + "data_index_image": "图片索引", + "data_index_num": "索引 {{index}}", + "data_index_question": "推测问题索引", + "data_index_summary": "摘要索引", "data_process_params": "处理参数", "data_process_setting": "数据处理配置", "dataset.Unsupported operation": "操作不支持", "dataset.no_collections": "暂无数据集", "dataset.no_tags": "暂无标签", + "default_params": "默认", + "default_params_desc": "使用系统默认的参数和规则", "edit_dataset_config": "编辑知识库配置", + "enhanced_indexes": "索引增强", "error.collectionNotFound": "集合找不到了~", "external_file": "外部文件库", "external_file_dataset_desc": "可以从外部文件库导入文件构建知识库,文件不会进行二次存储", @@ -38,19 +52,38 @@ "feishu_dataset": "飞书知识库", "feishu_dataset_config": "配置飞书知识库", "feishu_dataset_desc": "可通过配置飞书文档权限,使用飞书文档构建知识库,文档不会进行二次存储", + "file_list": "文件列表", "file_model_function_tip": "用于增强索引和 QA 生成", "filename": "文件名", "folder_dataset": "文件夹", "ideal_chunk_length": "理想分块长度", "ideal_chunk_length_tips": "按结束符号进行分段,并将多个分段组成一个分块,该值决定了分块的预估大小,如果会有上下浮动。", + "image_auto_parse": "图片自动索引", + "image_auto_parse_tips": "调用 VLM 自动标注文档里的图片,并生成额外的检索索引", "import.Auto mode Estimated Price Tips": "需调用文本理解模型,需要消耗较多AI 积分:{{price}} 积分/1K tokens", "import.Embedding Estimated Price Tips": "仅使用索引模型,消耗少量 AI 积分:{{price}} 积分/1K tokens", + "import_confirm": "确认上传", + "import_data_preview": "数据预览", + "import_data_process_setting": "数据处理方式设置", + "import_file_parse_setting": "文件解析设置", + "import_model_config": "模型选择", + "import_param_setting": "参数设置", + "import_select_file": "选择文件", "is_open_schedule": "启用定时同步", + "keep_image": "保留图片", "move.hint": "移动后,所选知识库/文件夹将继承新文件夹的权限设置,原先的权限设置失效。", "open_auto_sync": "开启定时同步后,系统将会每天不定时尝试同步集合,集合同步期间,会出现无法搜索到该集合数据现象。", + "params_setting": "参数设置", + "pdf_enhance_parse": "PDF增强解析", + "pdf_enhance_parse_price": "{{price}}积分/页", + "pdf_enhance_parse_tips": "调用 PDF 识别模型进行解析,可以将其转换成 Markdown 并保留文档中的图片,同时也可以对扫描件进行识别,识别时间较长。", "permission.des.manage": "可管理整个知识库数据和信息", "permission.des.read": "可查看知识库内容", "permission.des.write": "可增加和变更知识库内容", + "preview_chunk": "分块预览", + "preview_chunk_empty": "无法读取该文件内容", + "preview_chunk_intro": "最多展示 10 个分块", + "preview_chunk_not_selected": "点击左侧文件后进行预览", "rebuild_embedding_start_tip": "切换索引模型任务已开始", "rebuilding_index_count": "重建中索引数量:{{count}}", "request_headers": "请求头参数,会自动补充 Bearer", @@ -72,8 +105,10 @@ "tag.tags": "标签", "tag.total_tags": "共{{total}}个标签", "the_knowledge_base_has_indexes_that_are_being_trained_or_being_rebuilt": "知识库有训练中或正在重建的索引", + "total_num_files": "共 {{total}} 个文件", "training_mode": "处理方式", "vector_model_max_tokens_tip": "每个分块数据,最大长度为 3000 tokens", + "vllm_model": "图片理解模型", "website_dataset": "Web 站点同步", "website_dataset_desc": "Web 站点同步允许你直接使用一个网页链接构建知识库", "yuque_dataset": "语雀知识库", diff --git a/packages/web/i18n/zh-Hant/account.json b/packages/web/i18n/zh-Hant/account.json index 1b0894537b72..faf7b86f1198 100644 --- a/packages/web/i18n/zh-Hant/account.json +++ b/packages/web/i18n/zh-Hant/account.json @@ -3,7 +3,7 @@ "add_default_model": "新增預設模型", "api_key": "API 金鑰", "bills_and_invoices": "帳單與發票", - "channel": "頻道", + "channel": "模型渠道", "config_model": "模型配置", "confirm_logout": "確認登出登入?", "create_channel": "新增頻道", diff --git a/packages/web/i18n/zh-Hant/account_model.json b/packages/web/i18n/zh-Hant/account_model.json new file mode 100644 index 000000000000..4831a521f939 --- /dev/null +++ b/packages/web/i18n/zh-Hant/account_model.json @@ -0,0 +1,49 @@ +{ + "api_key": "API 密鑰", + "azure": "Azure", + "base_url": "代理地址", + "channel_name": "渠道名", + "channel_priority": "優先級", + "channel_priority_tip": "優先級越高的渠道,越容易被請求到", + "channel_status": "狀態", + "channel_status_auto_disabled": "自動禁用", + "channel_status_disabled": "禁用", + "channel_status_enabled": "啟用", + "channel_status_unknown": "未知", + "channel_type": "廠商", + "clear_model": "清空模型", + "copy_model_id_success": "已復制模型id", + "create_channel": "新增渠道", + "default_url": "默認地址", + "detail": "詳情", + "edit_channel": "渠道配置", + "enable_channel": "啟用", + "forbid_channel": "禁用", + "maxToken_tip": "模型 max_tokens 參數,如果留空,則代表模型不支持該參數。", + "key_type": "API key 格式:", + "log": "調用日誌", + "log_detail": "日誌詳情", + "log_request_id_search": "根據 requestId 搜索", + "log_status": "狀態", + "mapping": "模型映射", + "mapping_tip": "需填寫一個有效 Json。\n可在向實際地址發送請求時,對模型進行映射。\n例如:\n{\n \n \"gpt-4o\": \"gpt-4o-test\"\n\n}\n\n當 FastGPT 請求 gpt-4o 模型時,會向實際地址發送 gpt-4o-test 的模型,而不是 gpt-4o。", + "max_temperature_tip": "模型 temperature 參數,不填則代表模型不支持 temperature 參數。", + "model": "模型", + "model_name": "模型名", + "model_test": "模型測試", + "model_tokens": "輸入/輸出 Tokens", + "request_at": "請求時間", + "request_duration": "請求時長: {{duration}}s", + "running_test": "測試中", + "search_model": "搜索模型", + "select_channel": "選擇渠道名", + "select_model": "選擇模型", + "select_model_placeholder": "選擇該渠道下可用的模型", + "select_provider_placeholder": "搜索廠商", + "selected_model_empty": "至少選擇一個模型", + "start_test": "開始測試{{num}}個模型", + "test_failed": "有{{num}}個模型報錯", + "vlm_model": "圖片理解模型", + "vlm_model_tip": "用於知識庫中對文檔中的圖片進行額外的索引生成", + "waiting_test": "等待測試" +} diff --git a/packages/web/i18n/zh-Hant/account_usage.json b/packages/web/i18n/zh-Hant/account_usage.json index dd6699c6ddef..4c8e276c818c 100644 --- a/packages/web/i18n/zh-Hant/account_usage.json +++ b/packages/web/i18n/zh-Hant/account_usage.json @@ -2,6 +2,7 @@ "ai_model": "AI 模型", "all": "所有", "app_name": "應用程式名", + "auto_index": "索引增強", "billing_module": "扣費模組", "confirm_export": "共篩選出 {{total}} 條數據,是否確認導出?", "current_filter_conditions": "當前篩選條件:", @@ -9,6 +10,7 @@ "details": "詳情", "dingtalk": "釘釘", "duration_seconds": "時長(秒)", + "embedding_index": "索引生成", "every_day": "天", "every_month": "月", "export_confirm": "導出確認", @@ -16,6 +18,7 @@ "export_title": "時間,成員,類型,項目名,AI 積分消耗", "feishu": "飛書", "generation_time": "生成時間", + "image_parse": "圖片標註", "input_token_length": "輸入 tokens", "member": "成員", "member_name": "成員名", @@ -25,8 +28,12 @@ "official_account": "公眾號", "order_number": "訂單編號", "output_token_length": "輸出 tokens", + "pages": "頁數", + "pdf_enhanced_parse": "PDF 增強解析", + "pdf_parse": "PDF 解析", "points": "積分", "project_name": "專案名", + "qa": "問答對提取", "select_member_and_source_first": "請先選取成員和類型", "share": "分享連結", "source": "來源", diff --git a/packages/web/i18n/zh-Hant/app.json b/packages/web/i18n/zh-Hant/app.json index 8173ff64212a..dfa6bfc7d9ac 100644 --- a/packages/web/i18n/zh-Hant/app.json +++ b/packages/web/i18n/zh-Hant/app.json @@ -105,6 +105,9 @@ "open_vision_function_tip": "有圖示開關的模型即擁有圖片辨識功能。若開啟,模型會解析檔案連結中的圖片,並自動解析使用者問題中的圖片(使用者問題 ≤ 500 字時生效)。", "or_drag_JSON": "或拖曳 JSON 檔案", "paste_config_or_drag": "貼上配置或拖入 JSON 文件", + "pdf_enhance_parse": "PDF增強解析", + "pdf_enhance_parse_price": "{{price}}積分/頁", + "pdf_enhance_parse_tips": "調用 PDF 識別模型進行解析,可以將其轉換成 Markdown 並保留文檔中的圖片,同時也可以對掃描件進行識別,識別時間較長。", "permission.des.manage": "在寫入權限基礎上,可以設定發布通道、檢視對話紀錄、分配這個應用程式的權限", "permission.des.read": "可以使用這個應用程式進行對話", "permission.des.write": "可以檢視和編輯應用程式", diff --git a/packages/web/i18n/zh-Hant/common.json b/packages/web/i18n/zh-Hant/common.json index 1e08fd6930e5..e3fd6e908f54 100644 --- a/packages/web/i18n/zh-Hant/common.json +++ b/packages/web/i18n/zh-Hant/common.json @@ -124,7 +124,6 @@ "common.Copy Successful": "複製成功", "common.Copy_failed": "複製失敗,請手動複製", "common.Create Failed": "建立失敗", - "common.Create New": "建立新項目", "common.Create Success": "建立成功", "common.Create Time": "建立時間", "common.Creating": "建立中", @@ -562,10 +561,7 @@ "core.dataset.file": "檔案", "core.dataset.folder": "目錄", "core.dataset.import.Auto mode Estimated Price Tips": "需要呼叫檔案處理模型,將消耗較多 AI 點數:{{price}} 點數/1K tokens", - "core.dataset.import.Auto process": "自動", - "core.dataset.import.Auto process desc": "自動設定分割和預處理規則", "core.dataset.import.Chunk Range": "範圍:{{min}}~{{max}}", - "core.dataset.import.Chunk Split": "直接分段", "core.dataset.import.Chunk Split Tip": "將文字依照特定規則進行分段處理後,轉換成可進行語意搜尋的格式,適合大多數場景。不需要呼叫模型額外處理,成本較低。", "core.dataset.import.Continue upload": "繼續上傳", "core.dataset.import.Custom process": "自訂規則", @@ -575,7 +571,6 @@ "core.dataset.import.Custom split char Tips": "允許您根據自訂的分隔符進行分割。通常用於已處理好的資料,使用特定的分隔符來精確分割。", "core.dataset.import.Custom text": "自訂文字", "core.dataset.import.Custom text desc": "手動輸入一段文字作為資料集", - "core.dataset.import.Data Preprocessing": "資料處理", "core.dataset.import.Data process params": "資料處理參數", "core.dataset.import.Down load csv template": "點選下載 CSV 範本", "core.dataset.import.Embedding Estimated Price Tips": "僅使用索引模型,消耗少量 AI 點數:{{price}} 點數/1K tokens", @@ -597,7 +592,6 @@ "core.dataset.import.Source name": "來源名稱", "core.dataset.import.Sources list": "來源列表", "core.dataset.import.Start upload": "開始上傳", - "core.dataset.import.Total files": "共 {{total}} 個檔案", "core.dataset.import.Upload complete": "上傳完成", "core.dataset.import.Upload data": "確認上傳", "core.dataset.import.Upload file progress": "檔案上傳進度", @@ -647,12 +641,12 @@ "core.dataset.test.test result placeholder": "測試結果將顯示在這裡", "core.dataset.test.test result tip": "根據知識庫內容與測試文字的相似度進行排序。您可以根據測試結果調整相應的文字。\n注意:測試記錄中的資料可能已經被修改。點選某筆測試資料後將顯示最新資料。", "core.dataset.training.Agent queue": "問答訓練排隊中", - "core.dataset.training.Auto mode": "增強處理", + "core.dataset.training.Auto mode": "補充索引", "core.dataset.training.Auto mode Tip": "透過子索引以及呼叫模型產生相關問題與摘要,來增加資料區塊的語意豐富度,更有利於檢索。需要消耗更多的儲存空間並增加 AI 呼叫次數。", - "core.dataset.training.Chunk mode": "直接分段", + "core.dataset.training.Chunk mode": "直接分块", "core.dataset.training.Full": "預計超過 5 分鐘", "core.dataset.training.Leisure": "閒置", - "core.dataset.training.QA mode": "問答拆分", + "core.dataset.training.QA mode": "問答對提取", "core.dataset.training.Vector queue": "索引排隊中", "core.dataset.training.Waiting": "預計 5 分鐘", "core.dataset.training.Website Sync": "網站同步", @@ -862,7 +856,6 @@ "dataset.collections.Select Collection": "選擇檔案", "dataset.collections.Select One Collection To Store": "選擇一個檔案進行儲存", "dataset.data.Can not edit": "無編輯權限", - "dataset.data.Custom Index Number": "自訂索引 {{number}}", "dataset.data.Default Index": "預設索引", "dataset.data.Delete Tip": "確認刪除此資料?", "dataset.data.Index Placeholder": "輸入索引文字內容", @@ -889,6 +882,10 @@ "error.upload_file_error_filename": "{{name}} 上傳失敗", "error.upload_image_error": "上傳文件失敗", "error.username_empty": "帳號不能為空", + "error_collection_not_exist": "集合不存在", + "error_embedding_not_config": "未配置索引模型", + "error_llm_not_config": "未配置文件理解模型", + "error_vlm_not_config": "未配置圖片理解模型", "extraction_results": "提取結果", "field_name": "欄位名稱", "free": "免費", @@ -955,6 +952,7 @@ "new_create": "建立新項目", "no": "否", "no_laf_env": "系統未設定 LAF 環境", + "not_model_config": "未配置相關模型", "not_yet_introduced": "暫無介紹", "option": "選項", "pay.amount": "金額", @@ -1120,7 +1118,6 @@ "support.wallet.invoice_detail": "發票詳細資訊", "support.wallet.invoice_info": "發票將在 3-7 個工作天內寄送至電子郵件信箱,請耐心等候", "support.wallet.invoicing": "開立發票", - "support.wallet.moduleName.index": "產生索引", "support.wallet.moduleName.qa": "問答拆分", "support.wallet.noBill": "無帳單紀錄", "support.wallet.no_invoice": "無發票紀錄", diff --git a/packages/web/i18n/zh-Hant/dataset.json b/packages/web/i18n/zh-Hant/dataset.json index e44d20974153..7eab0f0805db 100644 --- a/packages/web/i18n/zh-Hant/dataset.json +++ b/packages/web/i18n/zh-Hant/dataset.json @@ -3,11 +3,16 @@ "add_file": "新增文件", "api_file": "API 檔案庫", "api_url": "介面位址", + "auto_indexes": "自動生成補充索引", + "auto_indexes_tips": "通過大模型進行額外索引生成,提高語義豐富度,提高檢索的精度。", "chunk_max_tokens": "分塊上限", "close_auto_sync": "確認關閉自動同步功能?", "collection.Create update time": "建立/更新時間", "collection.Training type": "分段模式", + "collection.training_type": "處理模式", "collection_data_count": "數據量", + "collection_metadata_custom_pdf_parse": "PDF增強解析", + "collection_metadata_image_parse": "圖片標註", "collection_not_support_retraining": "此集合類型不支援重新調整參數", "collection_not_support_sync": "該集合不支援同步", "collection_sync": "立即同步", @@ -22,12 +27,21 @@ "custom_data_process_params_desc": "自訂資料處理規則", "data.ideal_chunk_length": "理想分塊長度", "data_amount": "{{dataAmount}} 組數據, {{indexAmount}} 組索引", + "data_index_custom": "自定義索引", + "data_index_default": "默認索引", + "data_index_image": "圖片索引", + "data_index_num": "索引 {{index}}", + "data_index_question": "推測問題索引", + "data_index_summary": "摘要索引", "data_process_params": "處理參數", "data_process_setting": "資料處理設定", "dataset.Unsupported operation": "操作不支持", "dataset.no_collections": "尚無資料集", "dataset.no_tags": "尚無標籤", + "default_params": "預設", + "default_params_desc": "使用系統默認的參數和規則", "edit_dataset_config": "編輯知識庫配置", + "enhanced_indexes": "索引增強", "error.collectionNotFound": "找不到集合", "external_file": "外部檔案庫", "external_file_dataset_desc": "可以從外部檔案庫匯入檔案建立資料集,檔案不會進行二次儲存", @@ -38,19 +52,38 @@ "feishu_dataset": "飛書知識庫", "feishu_dataset_config": "配置飛書知識庫", "feishu_dataset_desc": "可通過配置飛書文檔權限,使用飛書文檔構建知識庫,文檔不會進行二次存儲", + "file_list": "文件列表", "file_model_function_tip": "用於增強索引和問答生成", "filename": "檔案名稱", "folder_dataset": "資料夾", "ideal_chunk_length": "理想分塊長度", "ideal_chunk_length_tips": "依結束符號進行分段,並將多個分段組成一個分塊,此值決定了分塊的預估大小,可能會有上下浮動。", + "image_auto_parse": "圖片自動索引", + "image_auto_parse_tips": "調用 VLM 自動標註文檔裡的圖片,並生成額外的檢索索引", "import.Auto mode Estimated Price Tips": "需呼叫文字理解模型,將消耗較多 AI 點數:{{price}} 點數 / 1K tokens", "import.Embedding Estimated Price Tips": "僅使用索引模型,消耗少量 AI 點數:{{price}} 點數 / 1K tokens", + "import_confirm": "確認上傳", + "import_data_preview": "數據預覽", + "import_data_process_setting": "數據處理方式設置", + "import_file_parse_setting": "文件解析設置", + "import_model_config": "模型選擇", + "import_param_setting": "參數設置", + "import_select_file": "選擇文件", "is_open_schedule": "啟用定時同步", + "keep_image": "保留圖片", "move.hint": "移動後,所選資料集/資料夾將繼承新資料夾的權限設定,原先的權限設定將失效。", "open_auto_sync": "開啟定時同步後,系統將每天不定時嘗試同步集合,集合同步期間,會出現無法搜尋到該集合資料現象。", + "params_setting": "參數設置", + "pdf_enhance_parse": "PDF增強解析", + "pdf_enhance_parse_price": "{{price}}積分/頁", + "pdf_enhance_parse_tips": "調用 PDF 識別模型進行解析,可以將其轉換成 Markdown 並保留文檔中的圖片,同時也可以對掃描件進行識別,識別時間較長。", "permission.des.manage": "可管理整個資料集的資料和資訊", "permission.des.read": "可檢視資料集內容", "permission.des.write": "可新增和變更資料集內容", + "preview_chunk": "分塊預覽", + "preview_chunk_empty": "無法讀取該文件內容", + "preview_chunk_intro": "最多展示 10 個分塊", + "preview_chunk_not_selected": "點擊左側文件後進行預覽", "rebuild_embedding_start_tip": "切換索引模型任務已開始", "rebuilding_index_count": "重建中索引數量:{{count}}", "request_headers": "請求頭", @@ -72,8 +105,10 @@ "tag.tags": "標籤", "tag.total_tags": "共 {{total}} 個標籤", "the_knowledge_base_has_indexes_that_are_being_trained_or_being_rebuilt": "資料集有索引正在訓練或重建中", + "total_num_files": "共 {{total}} 個文件", "training_mode": "分段模式", "vector_model_max_tokens_tip": "每個分塊數據,最大長度為 3000 tokens", + "vllm_model": "圖片理解模型", "website_dataset": "網站同步", "website_dataset_desc": "網站同步功能讓您可以直接使用網頁連結建立資料集", "yuque_dataset": "語雀知識庫", diff --git a/packages/web/types/i18next.d.ts b/packages/web/types/i18next.d.ts index 13fa5c4426ab..629568740a53 100644 --- a/packages/web/types/i18next.d.ts +++ b/packages/web/types/i18next.d.ts @@ -18,6 +18,7 @@ import workflow from '../i18n/zh-CN/workflow.json'; import user from '../i18n/zh-CN/user.json'; import chat from '../i18n/zh-CN/chat.json'; import login from '../i18n/zh-CN/login.json'; +import account_model from '../i18n/zh-CN/account_model.json'; export interface I18nNamespaces { common: typeof common; @@ -39,6 +40,7 @@ export interface I18nNamespaces { account: typeof account; account_team: typeof account_team; account_thirdParty: typeof account_thirdParty; + account_model: typeof account_model; } export type I18nNsType = (keyof I18nNamespaces)[]; @@ -73,7 +75,8 @@ declare module 'i18next' { 'account_promotion', 'account_thirdParty', 'account', - 'account_team' + 'account_team', + 'account_model' ]; resources: I18nNamespaces; } diff --git a/plugins/README.md b/plugins/README.md new file mode 100644 index 000000000000..9d2defe87fb9 --- /dev/null +++ b/plugins/README.md @@ -0,0 +1,5 @@ +# 目录说明 + +该目录为 FastGPT 辅助子项目,非必须。 + +- model 私有化模型 \ No newline at end of file diff --git a/files/models/Baichuan2/openai_api.py b/plugins/model/llm-Baichuan2/openai_api.py similarity index 100% rename from files/models/Baichuan2/openai_api.py rename to plugins/model/llm-Baichuan2/openai_api.py diff --git a/files/models/Baichuan2/requirements.txt b/plugins/model/llm-Baichuan2/requirements.txt similarity index 100% rename from files/models/Baichuan2/requirements.txt rename to plugins/model/llm-Baichuan2/requirements.txt diff --git a/files/models/ChatGLM2/openai_api.py b/plugins/model/llm-ChatGLM2/openai_api.py similarity index 100% rename from files/models/ChatGLM2/openai_api.py rename to plugins/model/llm-ChatGLM2/openai_api.py diff --git a/files/models/ChatGLM2/requirements.txt b/plugins/model/llm-ChatGLM2/requirements.txt similarity index 100% rename from files/models/ChatGLM2/requirements.txt rename to plugins/model/llm-ChatGLM2/requirements.txt diff --git a/python/ocr/surya/Dockerfile b/plugins/model/ocr-surya/Dockerfile similarity index 100% rename from python/ocr/surya/Dockerfile rename to plugins/model/ocr-surya/Dockerfile diff --git a/python/ocr/surya/README.md b/plugins/model/ocr-surya/README.md similarity index 100% rename from python/ocr/surya/README.md rename to plugins/model/ocr-surya/README.md diff --git a/python/ocr/surya/app.py b/plugins/model/ocr-surya/app.py similarity index 100% rename from python/ocr/surya/app.py rename to plugins/model/ocr-surya/app.py diff --git a/python/ocr/surya/requirements.txt b/plugins/model/ocr-surya/requirements.txt similarity index 100% rename from python/ocr/surya/requirements.txt rename to plugins/model/ocr-surya/requirements.txt diff --git a/python/pdf-marker/Dockerfile b/plugins/model/pdf-marker/Dockerfile similarity index 100% rename from python/pdf-marker/Dockerfile rename to plugins/model/pdf-marker/Dockerfile diff --git a/python/pdf-marker/Readme.md b/plugins/model/pdf-marker/Readme.md similarity index 79% rename from python/pdf-marker/Readme.md rename to plugins/model/pdf-marker/Readme.md index b296c12d3937..0a617335b13c 100644 --- a/python/pdf-marker/Readme.md +++ b/plugins/model/pdf-marker/Readme.md @@ -70,7 +70,7 @@ export PROCESSES_PER_GPU="1" python api_mp.py ``` -# 镜像打包和部署 +# 镜像打包和部署(推荐) ## 本地构建镜像 @@ -83,26 +83,39 @@ export PROCESSES_PER_GPU="1" ```bash sudo docker run --gpus all -itd -p 7231:7231 --name model_pdf_v1 -e PROCESSES_PER_GPU="2" model_pdf ``` -## 快速构建镜像 + +## 快速构建镜像(推荐) + ```dockerfile -docker pull crpi-h3snc261q1dosroc.cn-hangzhou.personal.cr.aliyuncs.com/marker11/marker_images:latest -docker run --gpus all -itd -p 7231:7231 --name model_pdf_v1 -e PROCESSES_PER_GPU="2" crpi-h3snc261q1dosroc.cn-hangzhou.personal.cr.aliyuncs.com/marker11/marker_images:latest +docker pull crpi-h3snc261q1dosroc.cn-hangzhou.personal.cr.aliyuncs.com/marker11/marker_images:v0.2 +docker run --gpus all -itd -p 7231:7232 --name model_pdf_v2 -e PROCESSES_PER_GPU="2" crpi-h3snc261q1dosroc.cn-hangzhou.personal.cr.aliyuncs.com/marker11/marker_images:v0.2 ``` -*注意*:参数PROCESSES_PER_GPU设置每张显卡上文件处理的并行数量,24G的显卡可以设置为2。在多显卡的环境中会自动切换显卡来运行多文件的并行处理。 + # 访问示例 -用Post方法访问端口为 `7321 ` 的 `v1/parse/file` 服务 +marker v0.1:用Post方法访问端口为 `7321 ` 的 `v1/parse/file` 服务 -参数:file-->本地文件的地址 +marker v0.2:用Post方法访问端口为 `7321 ` 的 `v2/parse/file` 服务 -- 访问方法 - ``` - curl --location --request POST "http://localhost:7231/v1/parse/file" \ - --header "Authorization: Bearer your_access_token" \ - --form "file=@./file/chinese_test.pdf" - ``` +- 访问方法 + + - v0.2 + ``` + curl --location --request POST "http://localhost:7231/v2/parse/file" \ + --header "Authorization: Bearer your_access_token" \ + --form "file=@./file/chinese_test.pdf" + ``` + - v0.1 + ``` + curl --location --request POST "http://localhost:7231/v1/parse/file" \ + --header "Authorization: Bearer your_access_token" \ + --form "file=@./file/chinese_test.pdf" + ``` + + 参数:file-->本地文件的地址 + - 多文件测试数据 运行 `test` 文件下的 `test.py` 文件,修改里面的 `file_paths` 为自己仓库的 `url` 即可 diff --git a/python/pdf-marker/api_mp.py b/plugins/model/pdf-marker/api_mp.py similarity index 100% rename from python/pdf-marker/api_mp.py rename to plugins/model/pdf-marker/api_mp.py diff --git a/python/pdf-marker/pip.conf b/plugins/model/pdf-marker/pip.conf similarity index 100% rename from python/pdf-marker/pip.conf rename to plugins/model/pdf-marker/pip.conf diff --git a/python/pdf-marker/requirements.txt b/plugins/model/pdf-marker/requirements.txt similarity index 100% rename from python/pdf-marker/requirements.txt rename to plugins/model/pdf-marker/requirements.txt diff --git a/python/pdf-marker/test/test.py b/plugins/model/pdf-marker/test/test.py similarity index 100% rename from python/pdf-marker/test/test.py rename to plugins/model/pdf-marker/test/test.py diff --git a/python/bge-rerank/README.md b/plugins/model/rerank-bge/README.md similarity index 100% rename from python/bge-rerank/README.md rename to plugins/model/rerank-bge/README.md diff --git a/python/bge-rerank/bge-reranker-base/Dockerfile b/plugins/model/rerank-bge/bge-reranker-base/Dockerfile similarity index 100% rename from python/bge-rerank/bge-reranker-base/Dockerfile rename to plugins/model/rerank-bge/bge-reranker-base/Dockerfile diff --git a/python/bge-rerank/bge-reranker-base/app.py b/plugins/model/rerank-bge/bge-reranker-base/app.py similarity index 100% rename from python/bge-rerank/bge-reranker-base/app.py rename to plugins/model/rerank-bge/bge-reranker-base/app.py diff --git a/python/bge-rerank/bge-reranker-base/requirements.txt b/plugins/model/rerank-bge/bge-reranker-base/requirements.txt similarity index 100% rename from python/bge-rerank/bge-reranker-base/requirements.txt rename to plugins/model/rerank-bge/bge-reranker-base/requirements.txt diff --git a/python/bge-rerank/bge-reranker-large/Dockerfile b/plugins/model/rerank-bge/bge-reranker-large/Dockerfile similarity index 100% rename from python/bge-rerank/bge-reranker-large/Dockerfile rename to plugins/model/rerank-bge/bge-reranker-large/Dockerfile diff --git a/python/bge-rerank/bge-reranker-large/app.py b/plugins/model/rerank-bge/bge-reranker-large/app.py similarity index 100% rename from python/bge-rerank/bge-reranker-large/app.py rename to plugins/model/rerank-bge/bge-reranker-large/app.py diff --git a/python/bge-rerank/bge-reranker-large/requirements.txt b/plugins/model/rerank-bge/bge-reranker-large/requirements.txt similarity index 100% rename from python/bge-rerank/bge-reranker-large/requirements.txt rename to plugins/model/rerank-bge/bge-reranker-large/requirements.txt diff --git a/python/bge-rerank/bge-reranker-v2-m3/Dockerfile b/plugins/model/rerank-bge/bge-reranker-v2-m3/Dockerfile similarity index 100% rename from python/bge-rerank/bge-reranker-v2-m3/Dockerfile rename to plugins/model/rerank-bge/bge-reranker-v2-m3/Dockerfile diff --git a/python/bge-rerank/bge-reranker-v2-m3/app.py b/plugins/model/rerank-bge/bge-reranker-v2-m3/app.py similarity index 100% rename from python/bge-rerank/bge-reranker-v2-m3/app.py rename to plugins/model/rerank-bge/bge-reranker-v2-m3/app.py diff --git a/python/bge-rerank/bge-reranker-v2-m3/requirements.txt b/plugins/model/rerank-bge/bge-reranker-v2-m3/requirements.txt similarity index 100% rename from python/bge-rerank/bge-reranker-v2-m3/requirements.txt rename to plugins/model/rerank-bge/bge-reranker-v2-m3/requirements.txt diff --git a/python/bge-rerank/rerank1.png b/plugins/model/rerank-bge/rerank1.png similarity index 100% rename from python/bge-rerank/rerank1.png rename to plugins/model/rerank-bge/rerank1.png diff --git a/python/sensevoice/Dockerfile b/plugins/model/stt-sensevoice/Dockerfile similarity index 100% rename from python/sensevoice/Dockerfile rename to plugins/model/stt-sensevoice/Dockerfile diff --git a/python/sensevoice/app/=1.13 b/plugins/model/stt-sensevoice/app/=1.13 similarity index 100% rename from python/sensevoice/app/=1.13 rename to plugins/model/stt-sensevoice/app/=1.13 diff --git a/python/sensevoice/app/iic/SenseVoiceSmall/.mdl b/plugins/model/stt-sensevoice/app/iic/SenseVoiceSmall/.mdl similarity index 100% rename from python/sensevoice/app/iic/SenseVoiceSmall/.mdl rename to plugins/model/stt-sensevoice/app/iic/SenseVoiceSmall/.mdl diff --git a/python/sensevoice/app/iic/SenseVoiceSmall/.msc b/plugins/model/stt-sensevoice/app/iic/SenseVoiceSmall/.msc similarity index 100% rename from python/sensevoice/app/iic/SenseVoiceSmall/.msc rename to plugins/model/stt-sensevoice/app/iic/SenseVoiceSmall/.msc diff --git a/python/sensevoice/app/iic/SenseVoiceSmall/.mv b/plugins/model/stt-sensevoice/app/iic/SenseVoiceSmall/.mv similarity index 100% rename from python/sensevoice/app/iic/SenseVoiceSmall/.mv rename to plugins/model/stt-sensevoice/app/iic/SenseVoiceSmall/.mv diff --git a/python/sensevoice/app/iic/SenseVoiceSmall/README.md b/plugins/model/stt-sensevoice/app/iic/SenseVoiceSmall/README.md similarity index 100% rename from python/sensevoice/app/iic/SenseVoiceSmall/README.md rename to plugins/model/stt-sensevoice/app/iic/SenseVoiceSmall/README.md diff --git a/python/sensevoice/app/iic/SenseVoiceSmall/am.mvn b/plugins/model/stt-sensevoice/app/iic/SenseVoiceSmall/am.mvn similarity index 100% rename from python/sensevoice/app/iic/SenseVoiceSmall/am.mvn rename to plugins/model/stt-sensevoice/app/iic/SenseVoiceSmall/am.mvn diff --git a/python/sensevoice/app/iic/SenseVoiceSmall/chn_jpn_yue_eng_ko_spectok.bpe.model b/plugins/model/stt-sensevoice/app/iic/SenseVoiceSmall/chn_jpn_yue_eng_ko_spectok.bpe.model similarity index 100% rename from python/sensevoice/app/iic/SenseVoiceSmall/chn_jpn_yue_eng_ko_spectok.bpe.model rename to plugins/model/stt-sensevoice/app/iic/SenseVoiceSmall/chn_jpn_yue_eng_ko_spectok.bpe.model diff --git a/python/sensevoice/app/iic/SenseVoiceSmall/config.yaml b/plugins/model/stt-sensevoice/app/iic/SenseVoiceSmall/config.yaml similarity index 100% rename from python/sensevoice/app/iic/SenseVoiceSmall/config.yaml rename to plugins/model/stt-sensevoice/app/iic/SenseVoiceSmall/config.yaml diff --git a/python/sensevoice/app/iic/SenseVoiceSmall/configuration.json b/plugins/model/stt-sensevoice/app/iic/SenseVoiceSmall/configuration.json similarity index 100% rename from python/sensevoice/app/iic/SenseVoiceSmall/configuration.json rename to plugins/model/stt-sensevoice/app/iic/SenseVoiceSmall/configuration.json diff --git a/python/sensevoice/app/iic/SenseVoiceSmall/fig/aed_figure.png b/plugins/model/stt-sensevoice/app/iic/SenseVoiceSmall/fig/aed_figure.png similarity index 100% rename from python/sensevoice/app/iic/SenseVoiceSmall/fig/aed_figure.png rename to plugins/model/stt-sensevoice/app/iic/SenseVoiceSmall/fig/aed_figure.png diff --git a/python/sensevoice/app/iic/SenseVoiceSmall/fig/asr_results.png b/plugins/model/stt-sensevoice/app/iic/SenseVoiceSmall/fig/asr_results.png similarity index 100% rename from python/sensevoice/app/iic/SenseVoiceSmall/fig/asr_results.png rename to plugins/model/stt-sensevoice/app/iic/SenseVoiceSmall/fig/asr_results.png diff --git a/python/sensevoice/app/iic/SenseVoiceSmall/fig/inference.png b/plugins/model/stt-sensevoice/app/iic/SenseVoiceSmall/fig/inference.png similarity index 100% rename from python/sensevoice/app/iic/SenseVoiceSmall/fig/inference.png rename to plugins/model/stt-sensevoice/app/iic/SenseVoiceSmall/fig/inference.png diff --git a/python/sensevoice/app/iic/SenseVoiceSmall/fig/sensevoice.png b/plugins/model/stt-sensevoice/app/iic/SenseVoiceSmall/fig/sensevoice.png similarity index 100% rename from python/sensevoice/app/iic/SenseVoiceSmall/fig/sensevoice.png rename to plugins/model/stt-sensevoice/app/iic/SenseVoiceSmall/fig/sensevoice.png diff --git a/python/sensevoice/app/iic/SenseVoiceSmall/fig/ser_figure.png b/plugins/model/stt-sensevoice/app/iic/SenseVoiceSmall/fig/ser_figure.png similarity index 100% rename from python/sensevoice/app/iic/SenseVoiceSmall/fig/ser_figure.png rename to plugins/model/stt-sensevoice/app/iic/SenseVoiceSmall/fig/ser_figure.png diff --git a/python/sensevoice/app/iic/SenseVoiceSmall/fig/ser_table.png b/plugins/model/stt-sensevoice/app/iic/SenseVoiceSmall/fig/ser_table.png similarity index 100% rename from python/sensevoice/app/iic/SenseVoiceSmall/fig/ser_table.png rename to plugins/model/stt-sensevoice/app/iic/SenseVoiceSmall/fig/ser_table.png diff --git a/python/sensevoice/app/iic/speech_fsmn_vad_zh-cn-16k-common-pytorch/.mdl b/plugins/model/stt-sensevoice/app/iic/speech_fsmn_vad_zh-cn-16k-common-pytorch/.mdl similarity index 100% rename from python/sensevoice/app/iic/speech_fsmn_vad_zh-cn-16k-common-pytorch/.mdl rename to plugins/model/stt-sensevoice/app/iic/speech_fsmn_vad_zh-cn-16k-common-pytorch/.mdl diff --git a/python/sensevoice/app/iic/speech_fsmn_vad_zh-cn-16k-common-pytorch/.msc b/plugins/model/stt-sensevoice/app/iic/speech_fsmn_vad_zh-cn-16k-common-pytorch/.msc similarity index 100% rename from python/sensevoice/app/iic/speech_fsmn_vad_zh-cn-16k-common-pytorch/.msc rename to plugins/model/stt-sensevoice/app/iic/speech_fsmn_vad_zh-cn-16k-common-pytorch/.msc diff --git a/python/sensevoice/app/iic/speech_fsmn_vad_zh-cn-16k-common-pytorch/.mv b/plugins/model/stt-sensevoice/app/iic/speech_fsmn_vad_zh-cn-16k-common-pytorch/.mv similarity index 100% rename from python/sensevoice/app/iic/speech_fsmn_vad_zh-cn-16k-common-pytorch/.mv rename to plugins/model/stt-sensevoice/app/iic/speech_fsmn_vad_zh-cn-16k-common-pytorch/.mv diff --git a/python/sensevoice/app/iic/speech_fsmn_vad_zh-cn-16k-common-pytorch/README.md b/plugins/model/stt-sensevoice/app/iic/speech_fsmn_vad_zh-cn-16k-common-pytorch/README.md similarity index 100% rename from python/sensevoice/app/iic/speech_fsmn_vad_zh-cn-16k-common-pytorch/README.md rename to plugins/model/stt-sensevoice/app/iic/speech_fsmn_vad_zh-cn-16k-common-pytorch/README.md diff --git a/python/sensevoice/app/iic/speech_fsmn_vad_zh-cn-16k-common-pytorch/am.mvn b/plugins/model/stt-sensevoice/app/iic/speech_fsmn_vad_zh-cn-16k-common-pytorch/am.mvn similarity index 100% rename from python/sensevoice/app/iic/speech_fsmn_vad_zh-cn-16k-common-pytorch/am.mvn rename to plugins/model/stt-sensevoice/app/iic/speech_fsmn_vad_zh-cn-16k-common-pytorch/am.mvn diff --git a/python/sensevoice/app/iic/speech_fsmn_vad_zh-cn-16k-common-pytorch/config.yaml b/plugins/model/stt-sensevoice/app/iic/speech_fsmn_vad_zh-cn-16k-common-pytorch/config.yaml similarity index 100% rename from python/sensevoice/app/iic/speech_fsmn_vad_zh-cn-16k-common-pytorch/config.yaml rename to plugins/model/stt-sensevoice/app/iic/speech_fsmn_vad_zh-cn-16k-common-pytorch/config.yaml diff --git a/python/sensevoice/app/iic/speech_fsmn_vad_zh-cn-16k-common-pytorch/configuration.json b/plugins/model/stt-sensevoice/app/iic/speech_fsmn_vad_zh-cn-16k-common-pytorch/configuration.json similarity index 100% rename from python/sensevoice/app/iic/speech_fsmn_vad_zh-cn-16k-common-pytorch/configuration.json rename to plugins/model/stt-sensevoice/app/iic/speech_fsmn_vad_zh-cn-16k-common-pytorch/configuration.json diff --git a/python/sensevoice/app/iic/speech_fsmn_vad_zh-cn-16k-common-pytorch/example/vad_example.wav b/plugins/model/stt-sensevoice/app/iic/speech_fsmn_vad_zh-cn-16k-common-pytorch/example/vad_example.wav similarity index 100% rename from python/sensevoice/app/iic/speech_fsmn_vad_zh-cn-16k-common-pytorch/example/vad_example.wav rename to plugins/model/stt-sensevoice/app/iic/speech_fsmn_vad_zh-cn-16k-common-pytorch/example/vad_example.wav diff --git a/python/sensevoice/app/iic/speech_fsmn_vad_zh-cn-16k-common-pytorch/fig/struct.png b/plugins/model/stt-sensevoice/app/iic/speech_fsmn_vad_zh-cn-16k-common-pytorch/fig/struct.png similarity index 100% rename from python/sensevoice/app/iic/speech_fsmn_vad_zh-cn-16k-common-pytorch/fig/struct.png rename to plugins/model/stt-sensevoice/app/iic/speech_fsmn_vad_zh-cn-16k-common-pytorch/fig/struct.png diff --git a/python/sensevoice/app/iic/speech_fsmn_vad_zh-cn-16k-common-pytorch/model.pt b/plugins/model/stt-sensevoice/app/iic/speech_fsmn_vad_zh-cn-16k-common-pytorch/model.pt similarity index 100% rename from python/sensevoice/app/iic/speech_fsmn_vad_zh-cn-16k-common-pytorch/model.pt rename to plugins/model/stt-sensevoice/app/iic/speech_fsmn_vad_zh-cn-16k-common-pytorch/model.pt diff --git a/python/sensevoice/app/main.py b/plugins/model/stt-sensevoice/app/main.py similarity index 100% rename from python/sensevoice/app/main.py rename to plugins/model/stt-sensevoice/app/main.py diff --git a/python/sensevoice/app/model.py b/plugins/model/stt-sensevoice/app/model.py similarity index 100% rename from python/sensevoice/app/model.py rename to plugins/model/stt-sensevoice/app/model.py diff --git a/python/sensevoice/app/requirements.txt b/plugins/model/stt-sensevoice/app/requirements.txt similarity index 100% rename from python/sensevoice/app/requirements.txt rename to plugins/model/stt-sensevoice/app/requirements.txt diff --git a/python/sensevoice/main.py b/plugins/model/stt-sensevoice/main.py similarity index 100% rename from python/sensevoice/main.py rename to plugins/model/stt-sensevoice/main.py diff --git a/python/sensevoice/run.sh b/plugins/model/stt-sensevoice/run.sh similarity index 100% rename from python/sensevoice/run.sh rename to plugins/model/stt-sensevoice/run.sh diff --git a/python/cosevoice/Dockerfile b/plugins/model/tts-cosevoice/Dockerfile similarity index 100% rename from python/cosevoice/Dockerfile rename to plugins/model/tts-cosevoice/Dockerfile diff --git a/python/cosevoice/fastapi/client.py b/plugins/model/tts-cosevoice/fastapi/client.py similarity index 100% rename from python/cosevoice/fastapi/client.py rename to plugins/model/tts-cosevoice/fastapi/client.py diff --git a/python/cosevoice/fastapi/server.py b/plugins/model/tts-cosevoice/fastapi/server.py similarity index 100% rename from python/cosevoice/fastapi/server.py rename to plugins/model/tts-cosevoice/fastapi/server.py diff --git a/python/cosevoice/grpc/client.py b/plugins/model/tts-cosevoice/grpc/client.py similarity index 100% rename from python/cosevoice/grpc/client.py rename to plugins/model/tts-cosevoice/grpc/client.py diff --git a/python/cosevoice/grpc/cosyvoice.proto b/plugins/model/tts-cosevoice/grpc/cosyvoice.proto similarity index 100% rename from python/cosevoice/grpc/cosyvoice.proto rename to plugins/model/tts-cosevoice/grpc/cosyvoice.proto diff --git a/python/cosevoice/grpc/server.py b/plugins/model/tts-cosevoice/grpc/server.py similarity index 100% rename from python/cosevoice/grpc/server.py rename to plugins/model/tts-cosevoice/grpc/server.py diff --git a/python/cosevoice/requirements.txt b/plugins/model/tts-cosevoice/requirements.txt similarity index 100% rename from python/cosevoice/requirements.txt rename to plugins/model/tts-cosevoice/requirements.txt diff --git a/plugins/webcrawler/.dockerignore b/plugins/webcrawler/.dockerignore new file mode 100644 index 000000000000..be1b0d08d9bb --- /dev/null +++ b/plugins/webcrawler/.dockerignore @@ -0,0 +1,3 @@ +# 忽略 .git 目录及其内容 +.git +.gitignore diff --git a/plugins/webcrawler/.gitignore b/plugins/webcrawler/.gitignore new file mode 100644 index 000000000000..f2987afc06a2 --- /dev/null +++ b/plugins/webcrawler/.gitignore @@ -0,0 +1,25 @@ +*~ + +searxng-docker.service +caddy +srv +searxng/uwsgi.ini +.env +SPIDER/.env + +# 忽略 node_modules 文件夹 +SPIDER/node_modules/ + +# 忽略构建输出文件夹 +SPIDER/dist/ + +# 忽略日志文件 +*.log + +# 忽略操作系统生成的文件 +.DS_Store +Thumbs.db + +# 忽略 IDE/编辑器生成的文件 +.vscode/ +.idea/ \ No newline at end of file diff --git a/plugins/webcrawler/.searchxng.env b/plugins/webcrawler/.searchxng.env new file mode 100644 index 000000000000..dbb62f560d63 --- /dev/null +++ b/plugins/webcrawler/.searchxng.env @@ -0,0 +1,14 @@ +# By default listen on https://localhost +# To change this: +# * uncomment SEARXNG_HOSTNAME, and replace by the SearXNG hostname +# * uncomment LETSENCRYPT_EMAIL, and replace by your email (require to create a Let's Encrypt certificate) + +# SEARXNG_HOSTNAME= +# LETSENCRYPT_EMAIL= + +# Optional: +# If you run a very small or a very large instance, you might want to change the amount of used uwsgi workers and threads per worker +# More workers (= processes) means that more search requests can be handled at the same time, but it also causes more resource usage + +SEARXNG_UWSGI_WORKERS=4 +SEARXNG_UWSGI_THREADS=4 diff --git a/plugins/webcrawler/Caddyfile b/plugins/webcrawler/Caddyfile new file mode 100644 index 000000000000..b9846a0c512a --- /dev/null +++ b/plugins/webcrawler/Caddyfile @@ -0,0 +1,91 @@ +{ + admin off + + log { + output stderr + format filter { + # Preserves first 8 bits from IPv4 and 32 bits from IPv6 + request>remote_ip ip_mask 8 32 + request>client_ip ip_mask 8 32 + + # Remove identificable information + request>remote_port delete + request>headers delete + request>uri query { + delete url + delete h + delete q + } + } + } +} + +{$SEARXNG_HOSTNAME} + +tls {$SEARXNG_TLS} + +encode zstd gzip + +@api { + path /config + path /healthz + path /stats/errors + path /stats/checker +} + +@search { + path /search +} + +@imageproxy { + path /image_proxy +} + +@static { + path /static/* +} + +header { + # CSP (https://content-security-policy.com) + Content-Security-Policy "upgrade-insecure-requests; default-src 'none'; script-src 'self'; style-src 'self' 'unsafe-inline'; form-action 'self' https://github.com/searxng/searxng/issues/new; font-src 'self'; frame-ancestors 'self'; base-uri 'self'; connect-src 'self' https://overpass-api.de; img-src * data:; frame-src https://www.youtube-nocookie.com https://player.vimeo.com https://www.dailymotion.com https://www.deezer.com https://www.mixcloud.com https://w.soundcloud.com https://embed.spotify.com;" + + # Disable some browser features + Permissions-Policy "accelerometer=(),camera=(),geolocation=(),gyroscope=(),magnetometer=(),microphone=(),payment=(),usb=()" + + # Set referrer policy + Referrer-Policy "no-referrer" + + # Force clients to use HTTPS + Strict-Transport-Security "max-age=31536000" + + # Prevent MIME type sniffing from the declared Content-Type + X-Content-Type-Options "nosniff" + + # X-Robots-Tag (comment to allow site indexing) + X-Robots-Tag "noindex, noarchive, nofollow" + + # Remove "Server" header + -Server +} + +header @api { + Access-Control-Allow-Methods "GET, OPTIONS" + Access-Control-Allow-Origin "*" +} + +route { + # Cache policy + header Cache-Control "max-age=0, no-store" + header @search Cache-Control "max-age=5, private" + header @imageproxy Cache-Control "max-age=604800, public" + header @static Cache-Control "max-age=31536000, public, immutable" +} + +# SearXNG (uWSGI) +reverse_proxy localhost:8080 { + header_up X-Forwarded-Port "" + header_up X-Real-IP "" + + # https://github.com/searx/searx-docker/issues/24 + header_up Connection "close" +} diff --git a/plugins/webcrawler/Dockerfile b/plugins/webcrawler/Dockerfile new file mode 100644 index 000000000000..a33d5374ec64 --- /dev/null +++ b/plugins/webcrawler/Dockerfile @@ -0,0 +1,57 @@ +FROM node:20.10.0-slim + +WORKDIR /app + +# 安装 Chrome 运行依赖 +RUN apt-get update && apt-get install -y \ + ca-certificates \ + fonts-liberation \ + libasound2 \ + libatk-bridge2.0-0 \ + libatk1.0-0 \ + libc6 \ + libcairo2 \ + libcups2 \ + libdbus-1-3 \ + libexpat1 \ + libfontconfig1 \ + libgbm1 \ + libgcc1 \ + libglib2.0-0 \ + libgtk-3-0 \ + libnspr4 \ + libnss3 \ + libpango-1.0-0 \ + libpangocairo-1.0-0 \ + libstdc++6 \ + libx11-6 \ + libx11-xcb1 \ + libxcb1 \ + libxcomposite1 \ + libxcursor1 \ + libxdamage1 \ + libxext6 \ + libxfixes3 \ + libxi6 \ + libxrandr2 \ + libxrender1 \ + libxss1 \ + libxtst6 \ + lsb-release \ + wget \ + xdg-utils \ + chromium \ + && rm -rf /var/lib/apt/lists/* + +# 安装中文字体 +RUN apt-get update && apt-get install -y fonts-wqy-microhei && fc-cache -f -v + +COPY SPIDER/. . + +RUN test -f package.json || (echo "package.json missing" && exit 1) +RUN test -f .env || (echo ".env file missing in SPIDER directory" && exit 1) + +RUN npm run build + +EXPOSE 3000 +CMD ["npm", "start"] \ No newline at end of file diff --git a/plugins/webcrawler/README.md b/plugins/webcrawler/README.md new file mode 100644 index 000000000000..56746d0e3946 --- /dev/null +++ b/plugins/webcrawler/README.md @@ -0,0 +1,73 @@ +# webcrawler +## docker版快速部署 + +## 代码版部署 +0. 按照 https://github.com/searxng/searxng-docker 的方式处理docker +1. 参考SPIDER文件夹下的.env.example,添加.env文件 +2. 进入SPIDER文件夹进行pnpm install +3. 回到根目录,运行docker compose up -d + +## 代码版开发 +1. 将docker-compose.yml中与SPIDER相关的部分注释掉(nodeapp) +2. .env文件中的URL参照注释修改 +3. 注释掉启动puppteer部分里面指定浏览器地址的代码 +4. pnpm run dev + + +## 测试样例: +Auth的Bear Token记得填,也就是.env里的ACCESS_TOKEN + +### 读取单页面(content以HTML形式返回) +``` +http://localhost:3000/api/read?queryUrl= +``` + +返回结构 +```json + +{ + "status": 200, + "data": { + "title": "something here", + "content": "something here" + } +} +{ + "status": 400, + "error": { + "code": "MISSING_PARAM", + "message": "缺少必要参数: query" + } +} +``` + +### 搜索(content以HTML形式返回) +``` +http://localhost:3000/api/search?query=&pageCount=5&needDetails=true&engine=baidu +``` + +```json +{ + "status": 200, + "data": { + "results": [ + { + "title": "string", + "url": "string", + "snippet": "string", + "source": "string", + "crawlStatus": "string", + "score": 0, + "content": "string" + } + ] + } +} +{ + "status": 400, + "error": { + "code": "MISSING_PARAM", + "message": "缺少必要参数: query" + } +} +``` \ No newline at end of file diff --git a/plugins/webcrawler/SPIDER/.env.example b/plugins/webcrawler/SPIDER/.env.example new file mode 100644 index 000000000000..0b928ac73148 --- /dev/null +++ b/plugins/webcrawler/SPIDER/.env.example @@ -0,0 +1,23 @@ + +ACCESS_TOKEN=114514 +DETECT_WEBSITE = zhuanlan.zhihu.com +STRATEGIES=[{"waitUntil":"networkidle0","timeout":5000},{"waitUntil":"networkidle2","timeout":10000},{"waitUntil":"load","timeout":15000}] +PORT=3000 +MAX_CONCURRENCY=10 +NODE_ENV=development +ENGINE = [ + +] + +ENGINE_BAIDUURL=https://www.baidu.com/s +#ENGINE_SEARCHXNGURL=http://localhost:8080/search +ENGINE_SEARCHXNGURL=http://searxng:8080/search + +#MONGODB_URI=mongodb://root:example@localhost:27017 +MONGODB_URI=mongodb://root:example@mongodb:27017 +BLACKLIST = [".gov.cn",".edu.cn"] + +STD_TTL=3600 +EXPIRE_AFTER_SECONDS=9000 + +#VALIDATE_PROXY=[{"ip":"","port":},{"ip":"","port":}] \ No newline at end of file diff --git a/plugins/webcrawler/SPIDER/package-lock.json b/plugins/webcrawler/SPIDER/package-lock.json new file mode 100644 index 000000000000..9c095e9ba609 --- /dev/null +++ b/plugins/webcrawler/SPIDER/package-lock.json @@ -0,0 +1,5804 @@ +{ + "name": "spider", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "spider", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "@types/node-fetch": "^2.6.12", + "assert": "^2.1.0", + "axios": "^1.7.9", + "body-parser": "^1.20.3", + "browserify-zlib": "^0.2.0", + "buffer": "^6.0.3", + "cheerio": "^1.0.0", + "crypto-browserify": "^3.12.1", + "dotenv": "^16.4.7", + "express": "^4.21.2", + "https-proxy-agent": "^7.0.6", + "jsdom": "^26.0.0", + "mongodb": "^6.13.1", + "node-cache": "^5.1.2", + "node-fetch": "^2.7.0", + "os-browserify": "^0.3.0", + "path-browserify": "^1.0.1", + "puppeteer": "^24.2.1", + "puppeteer-cluster": "^0.24.0", + "querystring-es3": "^0.2.1", + "random-useragent": "^0.5.0", + "spider": "file:", + "stream-browserify": "^3.0.0", + "stream-http": "^3.2.0", + "string_decoder": "^1.3.0", + "turndown": "^7.2.0", + "turndown-plugin-gfm": "^1.0.2", + "url": "^0.11.4", + "user-agents": "^1.1.454", + "util": "^0.12.5", + "vm-browserify": "^1.1.2" + }, + "devDependencies": { + "@types/body-parser": "^1.19.5", + "@types/express": "^5.0.0", + "@types/jsdom": "^21.1.7", + "@types/node": "^22.13.4", + "@types/random-useragent": "^0.3.3", + "@types/user-agents": "^1.0.4", + "ts-loader": "^9.5.2", + "ts-node-dev": "^2.0.0", + "typescript": "^5.7.3", + "webpack": "^5.98.0", + "webpack-cli": "^6.0.1", + "webpack-node-externals": "^3.0.0" + } + }, + "node_modules/@asamuzakjp/css-color": { + "version": "2.8.3", + "resolved": "https://registry.npmmirror.com/@asamuzakjp/css-color/-/css-color-2.8.3.tgz", + "integrity": "sha512-GIc76d9UI1hCvOATjZPyHFmE5qhRccp3/zGfMPapK3jBi+yocEzp6BBB0UnfRYP9NP4FANqUZYb0hnfs3TM3hw==", + "dependencies": { + "@csstools/css-calc": "^2.1.1", + "@csstools/css-color-parser": "^3.0.7", + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3", + "lru-cache": "^10.4.3" + } + }, + "node_modules/@asamuzakjp/css-color/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmmirror.com/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==" + }, + "node_modules/@babel/code-frame": { + "version": "7.26.2", + "resolved": "https://registry.npmmirror.com/@babel/code-frame/-/code-frame-7.26.2.tgz", + "integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==", + "dependencies": { + "@babel/helper-validator-identifier": "^7.25.9", + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.25.9", + "resolved": "https://registry.npmmirror.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", + "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmmirror.com/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@csstools/color-helpers": { + "version": "5.0.1", + "resolved": "https://registry.npmmirror.com/@csstools/color-helpers/-/color-helpers-5.0.1.tgz", + "integrity": "sha512-MKtmkA0BX87PKaO1NFRTFH+UnkgnmySQOvNxJubsadusqPEC2aJ9MOQiMceZJJ6oitUl/i0L6u0M1IrmAOmgBA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@csstools/css-calc": { + "version": "2.1.1", + "resolved": "https://registry.npmmirror.com/@csstools/css-calc/-/css-calc-2.1.1.tgz", + "integrity": "sha512-rL7kaUnTkL9K+Cvo2pnCieqNpTKgQzy5f+N+5Iuko9HAoasP+xgprVh7KN/MaJVvVL1l0EzQq2MoqBHKSrDrag==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3" + } + }, + "node_modules/@csstools/css-color-parser": { + "version": "3.0.7", + "resolved": "https://registry.npmmirror.com/@csstools/css-color-parser/-/css-color-parser-3.0.7.tgz", + "integrity": "sha512-nkMp2mTICw32uE5NN+EsJ4f5N+IGFeCFu4bGpiKgb2Pq/7J/MpyLBeQ5ry4KKtRFZaYs6sTmcMYrSRIyj5DFKA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "dependencies": { + "@csstools/color-helpers": "^5.0.1", + "@csstools/css-calc": "^2.1.1" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3" + } + }, + "node_modules/@csstools/css-parser-algorithms": { + "version": "3.0.4", + "resolved": "https://registry.npmmirror.com/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.4.tgz", + "integrity": "sha512-Up7rBoV77rv29d3uKHUIVubz1BTcgyUK72IvCQAbfbMv584xHcGKCKbWh7i8hPrRJ7qU4Y8IO3IY9m+iTB7P3A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-tokenizer": "^3.0.3" + } + }, + "node_modules/@csstools/css-tokenizer": { + "version": "3.0.3", + "resolved": "https://registry.npmmirror.com/@csstools/css-tokenizer/-/css-tokenizer-3.0.3.tgz", + "integrity": "sha512-UJnjoFsmxfKUdNYdWgOB0mWUypuLvAfQPH1+pyvRJs6euowbFkFC6P13w1l8mJyi3vxYMxc9kld5jZEGRQs6bw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@discoveryjs/json-ext": { + "version": "0.6.3", + "resolved": "https://registry.npmmirror.com/@discoveryjs/json-ext/-/json-ext-0.6.3.tgz", + "integrity": "sha512-4B4OijXeVNOPZlYA2oEwWOTkzyltLao+xbotHQeqN++Rv27Y6s818+n2Qkp8q+Fxhn0t/5lA5X1Mxktud8eayQ==", + "dev": true, + "engines": { + "node": ">=14.17.0" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.8", + "resolved": "https://registry.npmmirror.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", + "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", + "dev": true, + "dependencies": { + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/gen-mapping/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmmirror.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmmirror.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.2.1", + "resolved": "https://registry.npmmirror.com/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/source-map": { + "version": "0.3.6", + "resolved": "https://registry.npmmirror.com/@jridgewell/source-map/-/source-map-0.3.6.tgz", + "integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25" + } + }, + "node_modules/@jridgewell/source-map/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmmirror.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "resolved": "https://registry.npmmirror.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "dev": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmmirror.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "node_modules/@mixmark-io/domino": { + "version": "2.2.0", + "resolved": "https://registry.npmmirror.com/@mixmark-io/domino/-/domino-2.2.0.tgz", + "integrity": "sha512-Y28PR25bHXUg88kCV7nivXrP2Nj2RueZ3/l/jdx6J9f8J4nsEGcgX0Qe6lt7Pa+J79+kPiJU3LguR6O/6zrLOw==" + }, + "node_modules/@mongodb-js/saslprep": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/@mongodb-js/saslprep/-/saslprep-1.2.0.tgz", + "integrity": "sha512-+ywrb0AqkfaYuhHs6LxKWgqbh3I72EpEgESCw37o+9qPx9WTCkgDm2B+eMrwehGtHBWHFU4GXvnSCNiFhhausg==", + "dependencies": { + "sparse-bitfield": "^3.0.3" + } + }, + "node_modules/@puppeteer/browsers": { + "version": "2.7.1", + "resolved": "https://registry.npmmirror.com/@puppeteer/browsers/-/browsers-2.7.1.tgz", + "integrity": "sha512-MK7rtm8JjaxPN7Mf1JdZIZKPD2Z+W7osvrC1vjpvfOX1K0awDIHYbNi89f7eotp7eMUn2shWnt03HwVbriXtKQ==", + "dependencies": { + "debug": "^4.4.0", + "extract-zip": "^2.0.1", + "progress": "^2.0.3", + "proxy-agent": "^6.5.0", + "semver": "^7.7.0", + "tar-fs": "^3.0.8", + "yargs": "^17.7.2" + }, + "bin": { + "browsers": "lib/cjs/main-cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@puppeteer/browsers/node_modules/debug": { + "version": "4.4.0", + "resolved": "https://registry.npmmirror.com/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@puppeteer/browsers/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/@tootallnate/quickjs-emscripten": { + "version": "0.23.0", + "resolved": "https://registry.npmmirror.com/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz", + "integrity": "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==" + }, + "node_modules/@tsconfig/node10": { + "version": "1.0.11", + "resolved": "https://registry.npmmirror.com/@tsconfig/node10/-/node10-1.0.11.tgz", + "integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==", + "dev": true + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmmirror.com/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "dev": true + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "dev": true + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.4", + "resolved": "https://registry.npmmirror.com/@tsconfig/node16/-/node16-1.0.4.tgz", + "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", + "dev": true + }, + "node_modules/@types/body-parser": { + "version": "1.19.5", + "resolved": "https://registry.npmmirror.com/@types/body-parser/-/body-parser-1.19.5.tgz", + "integrity": "sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==", + "dev": true, + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmmirror.com/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/eslint": { + "version": "9.6.1", + "resolved": "https://registry.npmmirror.com/@types/eslint/-/eslint-9.6.1.tgz", + "integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==", + "dev": true, + "dependencies": { + "@types/estree": "*", + "@types/json-schema": "*" + } + }, + "node_modules/@types/eslint-scope": { + "version": "3.7.7", + "resolved": "https://registry.npmmirror.com/@types/eslint-scope/-/eslint-scope-3.7.7.tgz", + "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", + "dev": true, + "dependencies": { + "@types/eslint": "*", + "@types/estree": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.6", + "resolved": "https://registry.npmmirror.com/@types/estree/-/estree-1.0.6.tgz", + "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", + "dev": true + }, + "node_modules/@types/express": { + "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/@types/express/-/express-5.0.0.tgz", + "integrity": "sha512-DvZriSMehGHL1ZNLzi6MidnsDhUZM/x2pRdDIKdwbUNqqwHxMlRdkxtn6/EPKyqKpHqTl/4nRZsRNLpZxZRpPQ==", + "dev": true, + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^5.0.0", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "5.0.6", + "resolved": "https://registry.npmmirror.com/@types/express-serve-static-core/-/express-serve-static-core-5.0.6.tgz", + "integrity": "sha512-3xhRnjJPkULekpSzgtoNYYcTWgEZkp4myc+Saevii5JPnHNvHMRlBSHDbs7Bh1iPPoVTERHEZXyhyLbMEsExsA==", + "dev": true, + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/http-errors": { + "version": "2.0.4", + "resolved": "https://registry.npmmirror.com/@types/http-errors/-/http-errors-2.0.4.tgz", + "integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==", + "dev": true + }, + "node_modules/@types/jsdom": { + "version": "21.1.7", + "resolved": "https://registry.npmmirror.com/@types/jsdom/-/jsdom-21.1.7.tgz", + "integrity": "sha512-yOriVnggzrnQ3a9OKOCxaVuSug3w3/SbOj5i7VwXWZEyUNl3bLF9V3MfxGbZKuwqJOQyRfqXyROBB1CoZLFWzA==", + "dev": true, + "dependencies": { + "@types/node": "*", + "@types/tough-cookie": "*", + "parse5": "^7.0.0" + } + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmmirror.com/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true + }, + "node_modules/@types/mime": { + "version": "1.3.5", + "resolved": "https://registry.npmmirror.com/@types/mime/-/mime-1.3.5.tgz", + "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", + "dev": true + }, + "node_modules/@types/node": { + "version": "22.13.4", + "resolved": "https://registry.npmmirror.com/@types/node/-/node-22.13.4.tgz", + "integrity": "sha512-ywP2X0DYtX3y08eFVx5fNIw7/uIv8hYUKgXoK8oayJlLnKcRfEYCxWMVE1XagUdVtCJlZT1AU4LXEABW+L1Peg==", + "dependencies": { + "undici-types": "~6.20.0" + } + }, + "node_modules/@types/node-fetch": { + "version": "2.6.12", + "resolved": "https://registry.npmmirror.com/@types/node-fetch/-/node-fetch-2.6.12.tgz", + "integrity": "sha512-8nneRWKCg3rMtF69nLQJnOYUcbafYeFSjqkw3jCRLsqkWFlHaoQrr5mXmofFGOx3DKn7UfmBMyov8ySvLRVldA==", + "dependencies": { + "@types/node": "*", + "form-data": "^4.0.0" + } + }, + "node_modules/@types/qs": { + "version": "6.9.18", + "resolved": "https://registry.npmmirror.com/@types/qs/-/qs-6.9.18.tgz", + "integrity": "sha512-kK7dgTYDyGqS+e2Q4aK9X3D7q234CIZ1Bv0q/7Z5IwRDoADNU81xXJK/YVyLbLTZCoIwUoDoffFeF+p/eIklAA==", + "dev": true + }, + "node_modules/@types/random-useragent": { + "version": "0.3.3", + "resolved": "https://registry.npmmirror.com/@types/random-useragent/-/random-useragent-0.3.3.tgz", + "integrity": "sha512-FsJ5opTEaHjWaOcfmA+Y74pNjiO2ggred9W5+e6SDDP7UCWVNFGRZAxIbMRceX0/pnffOt50D9HK+y13CfERcw==", + "dev": true + }, + "node_modules/@types/range-parser": { + "version": "1.2.7", + "resolved": "https://registry.npmmirror.com/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", + "dev": true + }, + "node_modules/@types/send": { + "version": "0.17.4", + "resolved": "https://registry.npmmirror.com/@types/send/-/send-0.17.4.tgz", + "integrity": "sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==", + "dev": true, + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "1.15.7", + "resolved": "https://registry.npmmirror.com/@types/serve-static/-/serve-static-1.15.7.tgz", + "integrity": "sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==", + "dev": true, + "dependencies": { + "@types/http-errors": "*", + "@types/node": "*", + "@types/send": "*" + } + }, + "node_modules/@types/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/@types/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-xevGOReSYGM7g/kUBZzPqCrR/KYAo+F0yiPc85WFTJa0MSLtyFTVTU6cJu/aV4mid7IffDIWqo69THF2o4JiEQ==", + "dev": true + }, + "node_modules/@types/strip-json-comments": { + "version": "0.0.30", + "resolved": "https://registry.npmmirror.com/@types/strip-json-comments/-/strip-json-comments-0.0.30.tgz", + "integrity": "sha512-7NQmHra/JILCd1QqpSzl8+mJRc8ZHz3uDm8YV1Ks9IhK0epEiTw8aIErbvH9PI+6XbqhyIQy3462nEsn7UVzjQ==", + "dev": true + }, + "node_modules/@types/tough-cookie": { + "version": "4.0.5", + "resolved": "https://registry.npmmirror.com/@types/tough-cookie/-/tough-cookie-4.0.5.tgz", + "integrity": "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==", + "dev": true + }, + "node_modules/@types/user-agents": { + "version": "1.0.4", + "resolved": "https://registry.npmmirror.com/@types/user-agents/-/user-agents-1.0.4.tgz", + "integrity": "sha512-AjeFc4oX5WPPflgKfRWWJfkEk7Wu82fnj1rROPsiqFt6yElpdGFg8Srtm/4PU4rA9UiDUZlruGPgcwTMQlwq4w==", + "dev": true + }, + "node_modules/@types/webidl-conversions": { + "version": "7.0.3", + "resolved": "https://registry.npmmirror.com/@types/webidl-conversions/-/webidl-conversions-7.0.3.tgz", + "integrity": "sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA==" + }, + "node_modules/@types/whatwg-url": { + "version": "11.0.5", + "resolved": "https://registry.npmmirror.com/@types/whatwg-url/-/whatwg-url-11.0.5.tgz", + "integrity": "sha512-coYR071JRaHa+xoEvvYqvnIHaVqaYrLPbsufM9BF63HkwI5Lgmy2QR8Q5K/lYDYo5AK82wOvSOS0UsLTpTG7uQ==", + "dependencies": { + "@types/webidl-conversions": "*" + } + }, + "node_modules/@types/yauzl": { + "version": "2.10.3", + "resolved": "https://registry.npmmirror.com/@types/yauzl/-/yauzl-2.10.3.tgz", + "integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==", + "optional": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@webassemblyjs/ast": { + "version": "1.14.1", + "resolved": "https://registry.npmmirror.com/@webassemblyjs/ast/-/ast-1.14.1.tgz", + "integrity": "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==", + "dev": true, + "dependencies": { + "@webassemblyjs/helper-numbers": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2" + } + }, + "node_modules/@webassemblyjs/floating-point-hex-parser": { + "version": "1.13.2", + "resolved": "https://registry.npmmirror.com/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz", + "integrity": "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-api-error": { + "version": "1.13.2", + "resolved": "https://registry.npmmirror.com/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz", + "integrity": "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-buffer": { + "version": "1.14.1", + "resolved": "https://registry.npmmirror.com/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz", + "integrity": "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-numbers": { + "version": "1.13.2", + "resolved": "https://registry.npmmirror.com/@webassemblyjs/helper-numbers/-/helper-numbers-1.13.2.tgz", + "integrity": "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==", + "dev": true, + "dependencies": { + "@webassemblyjs/floating-point-hex-parser": "1.13.2", + "@webassemblyjs/helper-api-error": "1.13.2", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/helper-wasm-bytecode": { + "version": "1.13.2", + "resolved": "https://registry.npmmirror.com/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz", + "integrity": "sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-wasm-section": { + "version": "1.14.1", + "resolved": "https://registry.npmmirror.com/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.14.1.tgz", + "integrity": "sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/wasm-gen": "1.14.1" + } + }, + "node_modules/@webassemblyjs/ieee754": { + "version": "1.13.2", + "resolved": "https://registry.npmmirror.com/@webassemblyjs/ieee754/-/ieee754-1.13.2.tgz", + "integrity": "sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==", + "dev": true, + "dependencies": { + "@xtuc/ieee754": "^1.2.0" + } + }, + "node_modules/@webassemblyjs/leb128": { + "version": "1.13.2", + "resolved": "https://registry.npmmirror.com/@webassemblyjs/leb128/-/leb128-1.13.2.tgz", + "integrity": "sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==", + "dev": true, + "dependencies": { + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/utf8": { + "version": "1.13.2", + "resolved": "https://registry.npmmirror.com/@webassemblyjs/utf8/-/utf8-1.13.2.tgz", + "integrity": "sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==", + "dev": true + }, + "node_modules/@webassemblyjs/wasm-edit": { + "version": "1.14.1", + "resolved": "https://registry.npmmirror.com/@webassemblyjs/wasm-edit/-/wasm-edit-1.14.1.tgz", + "integrity": "sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/helper-wasm-section": "1.14.1", + "@webassemblyjs/wasm-gen": "1.14.1", + "@webassemblyjs/wasm-opt": "1.14.1", + "@webassemblyjs/wasm-parser": "1.14.1", + "@webassemblyjs/wast-printer": "1.14.1" + } + }, + "node_modules/@webassemblyjs/wasm-gen": { + "version": "1.14.1", + "resolved": "https://registry.npmmirror.com/@webassemblyjs/wasm-gen/-/wasm-gen-1.14.1.tgz", + "integrity": "sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/ieee754": "1.13.2", + "@webassemblyjs/leb128": "1.13.2", + "@webassemblyjs/utf8": "1.13.2" + } + }, + "node_modules/@webassemblyjs/wasm-opt": { + "version": "1.14.1", + "resolved": "https://registry.npmmirror.com/@webassemblyjs/wasm-opt/-/wasm-opt-1.14.1.tgz", + "integrity": "sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/wasm-gen": "1.14.1", + "@webassemblyjs/wasm-parser": "1.14.1" + } + }, + "node_modules/@webassemblyjs/wasm-parser": { + "version": "1.14.1", + "resolved": "https://registry.npmmirror.com/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz", + "integrity": "sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-api-error": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/ieee754": "1.13.2", + "@webassemblyjs/leb128": "1.13.2", + "@webassemblyjs/utf8": "1.13.2" + } + }, + "node_modules/@webassemblyjs/wast-printer": { + "version": "1.14.1", + "resolved": "https://registry.npmmirror.com/@webassemblyjs/wast-printer/-/wast-printer-1.14.1.tgz", + "integrity": "sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webpack-cli/configtest": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/@webpack-cli/configtest/-/configtest-3.0.1.tgz", + "integrity": "sha512-u8d0pJ5YFgneF/GuvEiDA61Tf1VDomHHYMjv/wc9XzYj7nopltpG96nXN5dJRstxZhcNpV1g+nT6CydO7pHbjA==", + "dev": true, + "engines": { + "node": ">=18.12.0" + }, + "peerDependencies": { + "webpack": "^5.82.0", + "webpack-cli": "6.x.x" + } + }, + "node_modules/@webpack-cli/info": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/@webpack-cli/info/-/info-3.0.1.tgz", + "integrity": "sha512-coEmDzc2u/ffMvuW9aCjoRzNSPDl/XLuhPdlFRpT9tZHmJ/039az33CE7uH+8s0uL1j5ZNtfdv0HkfaKRBGJsQ==", + "dev": true, + "engines": { + "node": ">=18.12.0" + }, + "peerDependencies": { + "webpack": "^5.82.0", + "webpack-cli": "6.x.x" + } + }, + "node_modules/@webpack-cli/serve": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/@webpack-cli/serve/-/serve-3.0.1.tgz", + "integrity": "sha512-sbgw03xQaCLiT6gcY/6u3qBDn01CWw/nbaXl3gTdTFuJJ75Gffv3E3DBpgvY2fkkrdS1fpjaXNOmJlnbtKauKg==", + "dev": true, + "engines": { + "node": ">=18.12.0" + }, + "peerDependencies": { + "webpack": "^5.82.0", + "webpack-cli": "6.x.x" + }, + "peerDependenciesMeta": { + "webpack-dev-server": { + "optional": true + } + } + }, + "node_modules/@xtuc/ieee754": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/@xtuc/ieee754/-/ieee754-1.2.0.tgz", + "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", + "dev": true + }, + "node_modules/@xtuc/long": { + "version": "4.2.2", + "resolved": "https://registry.npmmirror.com/@xtuc/long/-/long-4.2.2.tgz", + "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", + "dev": true + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmmirror.com/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/acorn": { + "version": "8.14.0", + "resolved": "https://registry.npmmirror.com/acorn/-/acorn-8.14.0.tgz", + "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.3.4", + "resolved": "https://registry.npmmirror.com/acorn-walk/-/acorn-walk-8.3.4.tgz", + "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", + "dev": true, + "dependencies": { + "acorn": "^8.11.0" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/agent-base": { + "version": "7.1.3", + "resolved": "https://registry.npmmirror.com/agent-base/-/agent-base-7.1.3.tgz", + "integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==", + "engines": { + "node": ">= 14" + } + }, + "node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmmirror.com/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmmirror.com/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "dev": true, + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmmirror.com/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.3" + }, + "peerDependencies": { + "ajv": "^8.8.2" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmmirror.com/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmmirror.com/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" + }, + "node_modules/asn1.js": { + "version": "4.10.1", + "resolved": "https://registry.npmmirror.com/asn1.js/-/asn1.js-4.10.1.tgz", + "integrity": "sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw==", + "dependencies": { + "bn.js": "^4.0.0", + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0" + } + }, + "node_modules/asn1.js/node_modules/bn.js": { + "version": "4.12.1", + "resolved": "https://registry.npmmirror.com/bn.js/-/bn.js-4.12.1.tgz", + "integrity": "sha512-k8TVBiPkPJT9uHLdOKfFpqcfprwBFOAAXXozRubr7R7PfIuKvQlzcI4M0pALeqXN09vdaMbUdUj+pass+uULAg==" + }, + "node_modules/assert": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/assert/-/assert-2.1.0.tgz", + "integrity": "sha512-eLHpSK/Y4nhMJ07gDaAzoX/XAKS8PSaojml3M0DM4JpV1LAi5JOJ/p6H/XWrl8L+DzVEvVCW1z3vWAaB9oTsQw==", + "dependencies": { + "call-bind": "^1.0.2", + "is-nan": "^1.3.2", + "object-is": "^1.1.5", + "object.assign": "^4.1.4", + "util": "^0.12.5" + } + }, + "node_modules/ast-types": { + "version": "0.13.4", + "resolved": "https://registry.npmmirror.com/ast-types/-/ast-types-0.13.4.tgz", + "integrity": "sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==", + "dependencies": { + "tslib": "^2.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmmirror.com/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmmirror.com/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/axios": { + "version": "1.7.9", + "resolved": "https://registry.npmmirror.com/axios/-/axios-1.7.9.tgz", + "integrity": "sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw==", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/b4a": { + "version": "1.6.7", + "resolved": "https://registry.npmmirror.com/b4a/-/b4a-1.6.7.tgz", + "integrity": "sha512-OnAYlL5b7LEkALw87fUVafQw5rVR9RjwGd4KUwNQ6DrrNmaVaUCgLipfVlzrPQ4tWOR9P0IXGNOx50jYCCdSJg==" + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/bare-events": { + "version": "2.5.4", + "resolved": "https://registry.npmmirror.com/bare-events/-/bare-events-2.5.4.tgz", + "integrity": "sha512-+gFfDkR8pj4/TrWCGUGWmJIkBwuxPS5F+a5yWjOHQt2hHvNZd5YLzadjmDUtFmMM4y429bnKLa8bYBMHcYdnQA==", + "optional": true + }, + "node_modules/bare-fs": { + "version": "4.0.1", + "resolved": "https://registry.npmmirror.com/bare-fs/-/bare-fs-4.0.1.tgz", + "integrity": "sha512-ilQs4fm/l9eMfWY2dY0WCIUplSUp7U0CT1vrqMg1MUdeZl4fypu5UP0XcDBK5WBQPJAKP1b7XEodISmekH/CEg==", + "optional": true, + "dependencies": { + "bare-events": "^2.0.0", + "bare-path": "^3.0.0", + "bare-stream": "^2.0.0" + }, + "engines": { + "bare": ">=1.7.0" + } + }, + "node_modules/bare-os": { + "version": "3.4.0", + "resolved": "https://registry.npmmirror.com/bare-os/-/bare-os-3.4.0.tgz", + "integrity": "sha512-9Ous7UlnKbe3fMi7Y+qh0DwAup6A1JkYgPnjvMDNOlmnxNRQvQ/7Nst+OnUQKzk0iAT0m9BisbDVp9gCv8+ETA==", + "optional": true, + "engines": { + "bare": ">=1.6.0" + } + }, + "node_modules/bare-path": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/bare-path/-/bare-path-3.0.0.tgz", + "integrity": "sha512-tyfW2cQcB5NN8Saijrhqn0Zh7AnFNsnczRcuWODH0eYAXBsJ5gVxAUuNr7tsHSC6IZ77cA0SitzT+s47kot8Mw==", + "optional": true, + "dependencies": { + "bare-os": "^3.0.1" + } + }, + "node_modules/bare-stream": { + "version": "2.6.5", + "resolved": "https://registry.npmmirror.com/bare-stream/-/bare-stream-2.6.5.tgz", + "integrity": "sha512-jSmxKJNJmHySi6hC42zlZnq00rga4jjxcgNZjY9N5WlOe/iOoGRtdwGsHzQv2RlH2KOYMwGUXhf2zXd32BA9RA==", + "optional": true, + "dependencies": { + "streamx": "^2.21.0" + }, + "peerDependencies": { + "bare-buffer": "*", + "bare-events": "*" + }, + "peerDependenciesMeta": { + "bare-buffer": { + "optional": true + }, + "bare-events": { + "optional": true + } + } + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmmirror.com/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/basic-ftp": { + "version": "5.0.5", + "resolved": "https://registry.npmmirror.com/basic-ftp/-/basic-ftp-5.0.5.tgz", + "integrity": "sha512-4Bcg1P8xhUuqcii/S0Z9wiHIrQVPMermM1any+MX5GeGD7faD3/msQUDGLol9wOcz4/jbg/WJnGqoJF6LiBdtg==", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmmirror.com/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/bn.js": { + "version": "5.2.1", + "resolved": "https://registry.npmmirror.com/bn.js/-/bn.js-5.2.1.tgz", + "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==" + }, + "node_modules/body-parser": { + "version": "1.20.3", + "resolved": "https://registry.npmmirror.com/body-parser/-/body-parser-1.20.3.tgz", + "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.13.0", + "raw-body": "2.5.2", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==" + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmmirror.com/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/brorand": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/brorand/-/brorand-1.1.0.tgz", + "integrity": "sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==" + }, + "node_modules/browserify-aes": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/browserify-aes/-/browserify-aes-1.2.0.tgz", + "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", + "dependencies": { + "buffer-xor": "^1.0.3", + "cipher-base": "^1.0.0", + "create-hash": "^1.1.0", + "evp_bytestokey": "^1.0.3", + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/browserify-cipher": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/browserify-cipher/-/browserify-cipher-1.0.1.tgz", + "integrity": "sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w==", + "dependencies": { + "browserify-aes": "^1.0.4", + "browserify-des": "^1.0.0", + "evp_bytestokey": "^1.0.0" + } + }, + "node_modules/browserify-des": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/browserify-des/-/browserify-des-1.0.2.tgz", + "integrity": "sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A==", + "dependencies": { + "cipher-base": "^1.0.1", + "des.js": "^1.0.0", + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "node_modules/browserify-rsa": { + "version": "4.1.1", + "resolved": "https://registry.npmmirror.com/browserify-rsa/-/browserify-rsa-4.1.1.tgz", + "integrity": "sha512-YBjSAiTqM04ZVei6sXighu679a3SqWORA3qZTEqZImnlkDIFtKc6pNutpjyZ8RJTjQtuYfeetkxM11GwoYXMIQ==", + "dependencies": { + "bn.js": "^5.2.1", + "randombytes": "^2.1.0", + "safe-buffer": "^5.2.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/browserify-sign": { + "version": "4.2.3", + "resolved": "https://registry.npmmirror.com/browserify-sign/-/browserify-sign-4.2.3.tgz", + "integrity": "sha512-JWCZW6SKhfhjJxO8Tyiiy+XYB7cqd2S5/+WeYHsKdNKFlCBhKbblba1A/HN/90YwtxKc8tCErjffZl++UNmGiw==", + "dependencies": { + "bn.js": "^5.2.1", + "browserify-rsa": "^4.1.0", + "create-hash": "^1.2.0", + "create-hmac": "^1.1.7", + "elliptic": "^6.5.5", + "hash-base": "~3.0", + "inherits": "^2.0.4", + "parse-asn1": "^5.1.7", + "readable-stream": "^2.3.8", + "safe-buffer": "^5.2.1" + }, + "engines": { + "node": ">= 0.12" + } + }, + "node_modules/browserify-zlib": { + "version": "0.2.0", + "resolved": "https://registry.npmmirror.com/browserify-zlib/-/browserify-zlib-0.2.0.tgz", + "integrity": "sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==", + "dependencies": { + "pako": "~1.0.5" + } + }, + "node_modules/browserslist": { + "version": "4.24.4", + "resolved": "https://registry.npmmirror.com/browserslist/-/browserslist-4.24.4.tgz", + "integrity": "sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "caniuse-lite": "^1.0.30001688", + "electron-to-chromium": "^1.5.73", + "node-releases": "^2.0.19", + "update-browserslist-db": "^1.1.1" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/bson": { + "version": "6.10.3", + "resolved": "https://registry.npmmirror.com/bson/-/bson-6.10.3.tgz", + "integrity": "sha512-MTxGsqgYTwfshYWTRdmZRC+M7FnG1b4y7RO7p2k3X24Wq0yv1m77Wsj0BzlPzd/IowgESfsruQCUToa7vbOpPQ==", + "engines": { + "node": ">=16.20.1" + } + }, + "node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmmirror.com/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmmirror.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", + "engines": { + "node": "*" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true + }, + "node_modules/buffer-xor": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/buffer-xor/-/buffer-xor-1.0.3.tgz", + "integrity": "sha512-571s0T7nZWK6vB67HI5dyUF7wXiNcfaPPPTl6zYCNApANjIvYJTg7hlud/+cJpdAhS7dVzqMLmfhfHR3rAcOjQ==" + }, + "node_modules/builtin-status-codes": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz", + "integrity": "sha512-HpGFw18DgFWlncDfjTa2rcQ4W88O1mC8e8yZ2AvQY5KDaktSTwo+KRf6nHK6FRI5FyRyb/5T6+TSxfP7QyGsmQ==" + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmmirror.com/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind": { + "version": "1.0.8", + "resolved": "https://registry.npmmirror.com/call-bind/-/call-bind-1.0.8.tgz", + "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", + "dependencies": { + "call-bind-apply-helpers": "^1.0.0", + "es-define-property": "^1.0.0", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/call-bound/-/call-bound-1.0.3.tgz", + "integrity": "sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA==", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001700", + "resolved": "https://registry.npmmirror.com/caniuse-lite/-/caniuse-lite-1.0.30001700.tgz", + "integrity": "sha512-2S6XIXwaE7K7erT8dY+kLQcpa5ms63XlRkMkReXjle+kf6c5g38vyMl+Z5y8dSxOFDhcFe+nxnn261PLxBSQsQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ] + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmmirror.com/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/cheerio": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/cheerio/-/cheerio-1.0.0.tgz", + "integrity": "sha512-quS9HgjQpdaXOvsZz82Oz7uxtXiy6UIsIQcpBj7HRw2M63Skasm9qlDocAM7jNuaxdhpPU7c4kJN+gA5MCu4ww==", + "dependencies": { + "cheerio-select": "^2.1.0", + "dom-serializer": "^2.0.0", + "domhandler": "^5.0.3", + "domutils": "^3.1.0", + "encoding-sniffer": "^0.2.0", + "htmlparser2": "^9.1.0", + "parse5": "^7.1.2", + "parse5-htmlparser2-tree-adapter": "^7.0.0", + "parse5-parser-stream": "^7.1.2", + "undici": "^6.19.5", + "whatwg-mimetype": "^4.0.0" + }, + "engines": { + "node": ">=18.17" + }, + "funding": { + "url": "https://github.com/cheeriojs/cheerio?sponsor=1" + } + }, + "node_modules/cheerio-select": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/cheerio-select/-/cheerio-select-2.1.0.tgz", + "integrity": "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==", + "dependencies": { + "boolbase": "^1.0.0", + "css-select": "^5.1.0", + "css-what": "^6.1.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmmirror.com/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chrome-trace-event": { + "version": "1.0.4", + "resolved": "https://registry.npmmirror.com/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz", + "integrity": "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==", + "dev": true, + "engines": { + "node": ">=6.0" + } + }, + "node_modules/chromium-bidi": { + "version": "1.3.0", + "resolved": "https://registry.npmmirror.com/chromium-bidi/-/chromium-bidi-1.3.0.tgz", + "integrity": "sha512-G3x1bkST13kmbL7+dT/oRkNH/7C4UqG+0YQpmySrzXspyOhYgDNc6lhSGpj3cuexvH25WTENhTYq2Tt9JRXtbw==", + "dependencies": { + "mitt": "^3.0.1", + "zod": "^3.24.1" + }, + "peerDependencies": { + "devtools-protocol": "*" + } + }, + "node_modules/cipher-base": { + "version": "1.0.6", + "resolved": "https://registry.npmmirror.com/cipher-base/-/cipher-base-1.0.6.tgz", + "integrity": "sha512-3Ek9H3X6pj5TgenXYtNWdaBon1tgYCaebd+XPg0keyjEbEfkD4KkmAxkQ/i1vYvxdcT5nscLBfq9VJRmCBcFSw==", + "dependencies": { + "inherits": "^2.0.4", + "safe-buffer": "^5.2.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmmirror.com/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/clone": { + "version": "2.1.2", + "resolved": "https://registry.npmmirror.com/clone/-/clone-2.1.2.tgz", + "integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/clone-deep": { + "version": "4.0.1", + "resolved": "https://registry.npmmirror.com/clone-deep/-/clone-deep-4.0.1.tgz", + "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", + "dev": true, + "dependencies": { + "is-plain-object": "^2.0.4", + "kind-of": "^6.0.2", + "shallow-clone": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmmirror.com/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/colorette": { + "version": "2.0.20", + "resolved": "https://registry.npmmirror.com/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", + "dev": true + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmmirror.com/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmmirror.com/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmmirror.com/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmmirror.com/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmmirror.com/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.7.1", + "resolved": "https://registry.npmmirror.com/cookie/-/cookie-0.7.1.tgz", + "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmmirror.com/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" + }, + "node_modules/cosmiconfig": { + "version": "9.0.0", + "resolved": "https://registry.npmmirror.com/cosmiconfig/-/cosmiconfig-9.0.0.tgz", + "integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==", + "dependencies": { + "env-paths": "^2.2.1", + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/create-ecdh": { + "version": "4.0.4", + "resolved": "https://registry.npmmirror.com/create-ecdh/-/create-ecdh-4.0.4.tgz", + "integrity": "sha512-mf+TCx8wWc9VpuxfP2ht0iSISLZnt0JgWlrOKZiNqyUZWnjIaCIVNQArMHnCZKfEYRg6IM7A+NeJoN8gf/Ws0A==", + "dependencies": { + "bn.js": "^4.1.0", + "elliptic": "^6.5.3" + } + }, + "node_modules/create-ecdh/node_modules/bn.js": { + "version": "4.12.1", + "resolved": "https://registry.npmmirror.com/bn.js/-/bn.js-4.12.1.tgz", + "integrity": "sha512-k8TVBiPkPJT9uHLdOKfFpqcfprwBFOAAXXozRubr7R7PfIuKvQlzcI4M0pALeqXN09vdaMbUdUj+pass+uULAg==" + }, + "node_modules/create-hash": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/create-hash/-/create-hash-1.2.0.tgz", + "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", + "dependencies": { + "cipher-base": "^1.0.1", + "inherits": "^2.0.1", + "md5.js": "^1.3.4", + "ripemd160": "^2.0.1", + "sha.js": "^2.4.0" + } + }, + "node_modules/create-hmac": { + "version": "1.1.7", + "resolved": "https://registry.npmmirror.com/create-hmac/-/create-hmac-1.1.7.tgz", + "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", + "dependencies": { + "cipher-base": "^1.0.3", + "create-hash": "^1.1.0", + "inherits": "^2.0.1", + "ripemd160": "^2.0.0", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" + } + }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmmirror.com/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/crypto-browserify": { + "version": "3.12.1", + "resolved": "https://registry.npmmirror.com/crypto-browserify/-/crypto-browserify-3.12.1.tgz", + "integrity": "sha512-r4ESw/IlusD17lgQi1O20Fa3qNnsckR126TdUuBgAu7GBYSIPvdNyONd3Zrxh0xCwA4+6w/TDArBPsMvhur+KQ==", + "dependencies": { + "browserify-cipher": "^1.0.1", + "browserify-sign": "^4.2.3", + "create-ecdh": "^4.0.4", + "create-hash": "^1.2.0", + "create-hmac": "^1.1.7", + "diffie-hellman": "^5.0.3", + "hash-base": "~3.0.4", + "inherits": "^2.0.4", + "pbkdf2": "^3.1.2", + "public-encrypt": "^4.0.3", + "randombytes": "^2.1.0", + "randomfill": "^1.0.4" + }, + "engines": { + "node": ">= 0.10" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/css-select": { + "version": "5.1.0", + "resolved": "https://registry.npmmirror.com/css-select/-/css-select-5.1.0.tgz", + "integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==", + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.1.0", + "domhandler": "^5.0.2", + "domutils": "^3.0.1", + "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/css-what": { + "version": "6.1.0", + "resolved": "https://registry.npmmirror.com/css-what/-/css-what-6.1.0.tgz", + "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==", + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/cssstyle": { + "version": "4.2.1", + "resolved": "https://registry.npmmirror.com/cssstyle/-/cssstyle-4.2.1.tgz", + "integrity": "sha512-9+vem03dMXG7gDmZ62uqmRiMRNtinIZ9ZyuF6BdxzfOD+FdN5hretzynkn0ReS2DO2GSw76RWHs0UmJPI2zUjw==", + "dependencies": { + "@asamuzakjp/css-color": "^2.8.2", + "rrweb-cssom": "^0.8.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/data-uri-to-buffer": { + "version": "6.0.2", + "resolved": "https://registry.npmmirror.com/data-uri-to-buffer/-/data-uri-to-buffer-6.0.2.tgz", + "integrity": "sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==", + "engines": { + "node": ">= 14" + } + }, + "node_modules/data-urls": { + "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/data-urls/-/data-urls-5.0.0.tgz", + "integrity": "sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==", + "dependencies": { + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^14.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmmirror.com/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/decimal.js": { + "version": "10.5.0", + "resolved": "https://registry.npmmirror.com/decimal.js/-/decimal.js-10.5.0.tgz", + "integrity": "sha512-8vDa8Qxvr/+d94hSh5P3IJwI5t8/c0KsMp+g8bNw9cY2icONa5aPfvKeieW1WlG0WQYwwhJ7mjui2xtiePQSXw==" + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmmirror.com/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmmirror.com/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/degenerator": { + "version": "5.0.1", + "resolved": "https://registry.npmmirror.com/degenerator/-/degenerator-5.0.1.tgz", + "integrity": "sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==", + "dependencies": { + "ast-types": "^0.13.4", + "escodegen": "^2.1.0", + "esprima": "^4.0.1" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/des.js": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/des.js/-/des.js-1.1.0.tgz", + "integrity": "sha512-r17GxjhUCjSRy8aiJpr8/UadFIzMzJGexI3Nmz4ADi9LYSFx4gTBp80+NaX/YsXWWLhpZ7v/v/ubEc/bCNfKwg==", + "dependencies": { + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/devtools-protocol": { + "version": "0.0.1402036", + "resolved": "https://registry.npmmirror.com/devtools-protocol/-/devtools-protocol-0.0.1402036.tgz", + "integrity": "sha512-JwAYQgEvm3yD45CHB+RmF5kMbWtXBaOGwuxa87sZogHcLCv8c/IqnThaoQ1y60d7pXWjSKWQphPEc+1rAScVdg==" + }, + "node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmmirror.com/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/diffie-hellman": { + "version": "5.0.3", + "resolved": "https://registry.npmmirror.com/diffie-hellman/-/diffie-hellman-5.0.3.tgz", + "integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==", + "dependencies": { + "bn.js": "^4.1.0", + "miller-rabin": "^4.0.0", + "randombytes": "^2.0.0" + } + }, + "node_modules/diffie-hellman/node_modules/bn.js": { + "version": "4.12.1", + "resolved": "https://registry.npmmirror.com/bn.js/-/bn.js-4.12.1.tgz", + "integrity": "sha512-k8TVBiPkPJT9uHLdOKfFpqcfprwBFOAAXXozRubr7R7PfIuKvQlzcI4M0pALeqXN09vdaMbUdUj+pass+uULAg==" + }, + "node_modules/dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmmirror.com/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ] + }, + "node_modules/domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmmirror.com/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "dependencies": { + "domelementtype": "^2.3.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "3.2.2", + "resolved": "https://registry.npmmirror.com/domutils/-/domutils-3.2.2.tgz", + "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==", + "dependencies": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/dotenv": { + "version": "16.4.7", + "resolved": "https://registry.npmmirror.com/dotenv/-/dotenv-16.4.7.tgz", + "integrity": "sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/dynamic-dedupe": { + "version": "0.3.0", + "resolved": "https://registry.npmmirror.com/dynamic-dedupe/-/dynamic-dedupe-0.3.0.tgz", + "integrity": "sha512-ssuANeD+z97meYOqd50e04Ze5qp4bPqo8cCkI4TRjZkzAUgIDTrXV1R8QCdINpiI+hw14+rYazvTRdQrz0/rFQ==", + "dev": true, + "dependencies": { + "xtend": "^4.0.0" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" + }, + "node_modules/electron-to-chromium": { + "version": "1.5.102", + "resolved": "https://registry.npmmirror.com/electron-to-chromium/-/electron-to-chromium-1.5.102.tgz", + "integrity": "sha512-eHhqaja8tE/FNpIiBrvBjFV/SSKpyWHLvxuR9dPTdo+3V9ppdLmFB7ZZQ98qNovcngPLYIz0oOBF9P0FfZef5Q==", + "dev": true + }, + "node_modules/elliptic": { + "version": "6.6.1", + "resolved": "https://registry.npmmirror.com/elliptic/-/elliptic-6.6.1.tgz", + "integrity": "sha512-RaddvvMatK2LJHqFJ+YA4WysVN5Ita9E35botqIYspQ4TkRAlCicdzKOjlyv/1Za5RyTNn7di//eEV0uTAfe3g==", + "dependencies": { + "bn.js": "^4.11.9", + "brorand": "^1.1.0", + "hash.js": "^1.0.0", + "hmac-drbg": "^1.0.1", + "inherits": "^2.0.4", + "minimalistic-assert": "^1.0.1", + "minimalistic-crypto-utils": "^1.0.1" + } + }, + "node_modules/elliptic/node_modules/bn.js": { + "version": "4.12.1", + "resolved": "https://registry.npmmirror.com/bn.js/-/bn.js-4.12.1.tgz", + "integrity": "sha512-k8TVBiPkPJT9uHLdOKfFpqcfprwBFOAAXXozRubr7R7PfIuKvQlzcI4M0pALeqXN09vdaMbUdUj+pass+uULAg==" + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmmirror.com/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/encoding-sniffer": { + "version": "0.2.0", + "resolved": "https://registry.npmmirror.com/encoding-sniffer/-/encoding-sniffer-0.2.0.tgz", + "integrity": "sha512-ju7Wq1kg04I3HtiYIOrUrdfdDvkyO9s5XM8QAj/bN61Yo/Vb4vgJxy5vi4Yxk01gWHbrofpPtpxM8bKger9jhg==", + "dependencies": { + "iconv-lite": "^0.6.3", + "whatwg-encoding": "^3.1.1" + }, + "funding": { + "url": "https://github.com/fb55/encoding-sniffer?sponsor=1" + } + }, + "node_modules/encoding-sniffer/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmmirror.com/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmmirror.com/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/enhanced-resolve": { + "version": "5.18.1", + "resolved": "https://registry.npmmirror.com/enhanced-resolve/-/enhanced-resolve-5.18.1.tgz", + "integrity": "sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmmirror.com/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmmirror.com/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "engines": { + "node": ">=6" + } + }, + "node_modules/envinfo": { + "version": "7.14.0", + "resolved": "https://registry.npmmirror.com/envinfo/-/envinfo-7.14.0.tgz", + "integrity": "sha512-CO40UI41xDQzhLB1hWyqUKgFhs250pNcGbyGKe1l/e4FSaI/+YE4IMG76GDt0In67WLPACIITC+sOi08x4wIvg==", + "dev": true, + "bin": { + "envinfo": "dist/cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmmirror.com/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmmirror.com/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-module-lexer": { + "version": "1.6.0", + "resolved": "https://registry.npmmirror.com/es-module-lexer/-/es-module-lexer-1.6.0.tgz", + "integrity": "sha512-qqnD1yMU6tk/jnaMosogGySTZP8YtUgAffA9nMN+E/rjxcfRQ6IEk7IiozUjgxKoFHBGjTLnrHB/YC45r/59EQ==", + "dev": true + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmmirror.com/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" + }, + "node_modules/escodegen": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/escodegen/-/escodegen-2.1.0.tgz", + "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==", + "dependencies": { + "esprima": "^4.0.1", + "estraverse": "^5.2.0", + "esutils": "^2.0.2" + }, + "bin": { + "escodegen": "bin/escodegen.js", + "esgenerate": "bin/esgenerate.js" + }, + "engines": { + "node": ">=6.0" + }, + "optionalDependencies": { + "source-map": "~0.6.1" + } + }, + "node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmmirror.com/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/eslint-scope/node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmmirror.com/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmmirror.com/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmmirror.com/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmmirror.com/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmmirror.com/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmmirror.com/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmmirror.com/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "dev": true, + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/evp_bytestokey": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz", + "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==", + "dependencies": { + "md5.js": "^1.3.4", + "safe-buffer": "^5.1.1" + } + }, + "node_modules/express": { + "version": "4.21.2", + "resolved": "https://registry.npmmirror.com/express/-/express-4.21.2.tgz", + "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.3", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.7.1", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.3.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.3", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.12", + "proxy-addr": "~2.0.7", + "qs": "6.13.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.19.0", + "serve-static": "1.16.2", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/extract-zip": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/extract-zip/-/extract-zip-2.0.1.tgz", + "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", + "dependencies": { + "debug": "^4.1.1", + "get-stream": "^5.1.0", + "yauzl": "^2.10.0" + }, + "bin": { + "extract-zip": "cli.js" + }, + "engines": { + "node": ">= 10.17.0" + }, + "optionalDependencies": { + "@types/yauzl": "^2.9.1" + } + }, + "node_modules/extract-zip/node_modules/debug": { + "version": "4.4.0", + "resolved": "https://registry.npmmirror.com/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/extract-zip/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmmirror.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "node_modules/fast-fifo": { + "version": "1.3.2", + "resolved": "https://registry.npmmirror.com/fast-fifo/-/fast-fifo-1.3.2.tgz", + "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==" + }, + "node_modules/fast-uri": { + "version": "3.0.6", + "resolved": "https://registry.npmmirror.com/fast-uri/-/fast-uri-3.0.6.tgz", + "integrity": "sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ] + }, + "node_modules/fastest-levenshtein": { + "version": "1.0.16", + "resolved": "https://registry.npmmirror.com/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz", + "integrity": "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==", + "dev": true, + "engines": { + "node": ">= 4.9.1" + } + }, + "node_modules/fd-slicer": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/fd-slicer/-/fd-slicer-1.1.0.tgz", + "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", + "dependencies": { + "pend": "~1.2.0" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmmirror.com/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/finalhandler": { + "version": "1.3.1", + "resolved": "https://registry.npmmirror.com/finalhandler/-/finalhandler-1.3.1.tgz", + "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmmirror.com/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/flat": { + "version": "5.0.2", + "resolved": "https://registry.npmmirror.com/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "dev": true, + "bin": { + "flat": "cli.js" + } + }, + "node_modules/follow-redirects": { + "version": "1.15.9", + "resolved": "https://registry.npmmirror.com/follow-redirects/-/follow-redirects-1.15.9.tgz", + "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/for-each": { + "version": "0.3.5", + "resolved": "https://registry.npmmirror.com/for-each/-/for-each-0.3.5.tgz", + "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", + "dependencies": { + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/form-data": { + "version": "4.0.2", + "resolved": "https://registry.npmmirror.com/form-data/-/form-data-4.0.2.tgz", + "integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmmirror.com/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmmirror.com/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmmirror.com/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.2.7", + "resolved": "https://registry.npmmirror.com/get-intrinsic/-/get-intrinsic-1.2.7.tgz", + "integrity": "sha512-VW6Pxhsrk0KAOqs3WEd0klDiF/+V7gQOpAvY1jVU/LHmaD/kQO4523aiJuikX/QAKYiW6x8Jh+RJej1almdtCA==", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "function-bind": "^1.1.2", + "get-proto": "^1.0.0", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmmirror.com/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-uri": { + "version": "6.0.4", + "resolved": "https://registry.npmmirror.com/get-uri/-/get-uri-6.0.4.tgz", + "integrity": "sha512-E1b1lFFLvLgak2whF2xDBcOy6NLVGZBqqjJjsIhvopKfWWEi64pLVTWWehV8KlLerZkfNTA95sTe2OdJKm1OzQ==", + "dependencies": { + "basic-ftp": "^5.0.2", + "data-uri-to-buffer": "^6.0.2", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/get-uri/node_modules/debug": { + "version": "4.4.0", + "resolved": "https://registry.npmmirror.com/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/get-uri/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmmirror.com/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmmirror.com/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/glob-to-regexp": { + "version": "0.4.1", + "resolved": "https://registry.npmmirror.com/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", + "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", + "dev": true + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmmirror.com/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hash-base": { + "version": "3.0.5", + "resolved": "https://registry.npmmirror.com/hash-base/-/hash-base-3.0.5.tgz", + "integrity": "sha512-vXm0l45VbcHEVlTCzs8M+s0VeYsB2lnlAaThoLKGXr3bE/VWDOelNUnycUPEhKEaXARL2TEFjBOyUiM6+55KBg==", + "dependencies": { + "inherits": "^2.0.4", + "safe-buffer": "^5.2.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/hash.js": { + "version": "1.1.7", + "resolved": "https://registry.npmmirror.com/hash.js/-/hash.js-1.1.7.tgz", + "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", + "dependencies": { + "inherits": "^2.0.3", + "minimalistic-assert": "^1.0.1" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hmac-drbg": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz", + "integrity": "sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==", + "dependencies": { + "hash.js": "^1.0.3", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.1" + } + }, + "node_modules/html-encoding-sniffer": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz", + "integrity": "sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==", + "dependencies": { + "whatwg-encoding": "^3.1.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/htmlparser2": { + "version": "9.1.0", + "resolved": "https://registry.npmmirror.com/htmlparser2/-/htmlparser2-9.1.0.tgz", + "integrity": "sha512-5zfg6mHUoaer/97TxnGpxmbR7zJtPwIYFMZ/H5ucTlPZhKvtum05yiPK3Mgai3a0DyVxv7qYqoweaEd2nrYQzQ==", + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.1.0", + "entities": "^4.5.0" + } + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmmirror.com/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/http-proxy-agent/node_modules/debug": { + "version": "4.4.0", + "resolved": "https://registry.npmmirror.com/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/http-proxy-agent/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmmirror.com/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/https-proxy-agent/node_modules/debug": { + "version": "4.4.0", + "resolved": "https://registry.npmmirror.com/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/https-proxy-agent/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmmirror.com/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmmirror.com/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmmirror.com/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/import-local": { + "version": "3.2.0", + "resolved": "https://registry.npmmirror.com/import-local/-/import-local-3.2.0.tgz", + "integrity": "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==", + "dev": true, + "dependencies": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + }, + "bin": { + "import-local-fixture": "fixtures/cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmmirror.com/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmmirror.com/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/interpret": { + "version": "3.1.1", + "resolved": "https://registry.npmmirror.com/interpret/-/interpret-3.1.1.tgz", + "integrity": "sha512-6xwYfHbajpoF0xLW+iwLkhwgvLoZDfjYfoFNu8ftMoXINzwuymNLd9u/KmwtdT2GbR+/Cz66otEGEVVUHX9QLQ==", + "dev": true, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/ip-address": { + "version": "9.0.5", + "resolved": "https://registry.npmmirror.com/ip-address/-/ip-address-9.0.5.tgz", + "integrity": "sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==", + "dependencies": { + "jsbn": "1.1.0", + "sprintf-js": "^1.1.3" + }, + "engines": { + "node": ">= 12" + } + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmmirror.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-arguments": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/is-arguments/-/is-arguments-1.2.0.tgz", + "integrity": "sha512-7bVbi0huj/wrIAOzb8U1aszg9kdi3KN/CyU19CTI7tAoZYEZoL9yCDXpbXN+uPsuWnP02cyug1gleqq+TU+YCA==", + "dependencies": { + "call-bound": "^1.0.2", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmmirror.com/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==" + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmmirror.com/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmmirror.com/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "dev": true, + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmmirror.com/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-generator-function": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/is-generator-function/-/is-generator-function-1.1.0.tgz", + "integrity": "sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ==", + "dependencies": { + "call-bound": "^1.0.3", + "get-proto": "^1.0.0", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmmirror.com/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-nan": { + "version": "1.3.2", + "resolved": "https://registry.npmmirror.com/is-nan/-/is-nan-1.3.2.tgz", + "integrity": "sha512-E+zBKpQ2t6MEo1VsonYmluk9NxGrbzpeeLC2xIViuO2EjU2xsXsBPwTr3Ykv9l08UYEVEdWeRZNouaZqF6RN0w==", + "dependencies": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmmirror.com/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmmirror.com/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dev": true, + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==" + }, + "node_modules/is-regex": { + "version": "1.2.1", + "resolved": "https://registry.npmmirror.com/is-regex/-/is-regex-1.2.1.tgz", + "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", + "dependencies": { + "call-bound": "^1.0.2", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.15", + "resolved": "https://registry.npmmirror.com/is-typed-array/-/is-typed-array-1.1.15.tgz", + "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", + "dependencies": { + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "node_modules/isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/jest-worker": { + "version": "27.5.1", + "resolved": "https://registry.npmmirror.com/jest-worker/-/jest-worker-27.5.1.tgz", + "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", + "dev": true, + "dependencies": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmmirror.com/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmmirror.com/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsbn": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/jsbn/-/jsbn-1.1.0.tgz", + "integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==" + }, + "node_modules/jsdom": { + "version": "26.0.0", + "resolved": "https://registry.npmmirror.com/jsdom/-/jsdom-26.0.0.tgz", + "integrity": "sha512-BZYDGVAIriBWTpIxYzrXjv3E/4u8+/pSG5bQdIYCbNCGOvsPkDQfTVLAIXAf9ETdCpduCVTkDe2NNZ8NIwUVzw==", + "dependencies": { + "cssstyle": "^4.2.1", + "data-urls": "^5.0.0", + "decimal.js": "^10.4.3", + "form-data": "^4.0.1", + "html-encoding-sniffer": "^4.0.0", + "http-proxy-agent": "^7.0.2", + "https-proxy-agent": "^7.0.6", + "is-potential-custom-element-name": "^1.0.1", + "nwsapi": "^2.2.16", + "parse5": "^7.2.1", + "rrweb-cssom": "^0.8.0", + "saxes": "^6.0.0", + "symbol-tree": "^3.2.4", + "tough-cookie": "^5.0.0", + "w3c-xmlserializer": "^5.0.0", + "webidl-conversions": "^7.0.0", + "whatwg-encoding": "^3.1.1", + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^14.1.0", + "ws": "^8.18.0", + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "canvas": "^3.0.0" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmmirror.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==" + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + }, + "node_modules/json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmmirror.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==" + }, + "node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmmirror.com/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmmirror.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==" + }, + "node_modules/loader-runner": { + "version": "4.3.0", + "resolved": "https://registry.npmmirror.com/loader-runner/-/loader-runner-4.3.0.tgz", + "integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==", + "dev": true, + "engines": { + "node": ">=6.11.5" + } + }, + "node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/lodash.clonedeep": { + "version": "4.5.0", + "resolved": "https://registry.npmmirror.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", + "integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==" + }, + "node_modules/lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmmirror.com/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "engines": { + "node": ">=12" + } + }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmmirror.com/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/md5.js": { + "version": "1.3.5", + "resolved": "https://registry.npmmirror.com/md5.js/-/md5.js-1.3.5.tgz", + "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", + "dependencies": { + "hash-base": "^3.0.0", + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmmirror.com/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/memory-pager": { + "version": "1.5.0", + "resolved": "https://registry.npmmirror.com/memory-pager/-/memory-pager-1.5.0.tgz", + "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==" + }, + "node_modules/merge-descriptors": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmmirror.com/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/miller-rabin": { + "version": "4.0.1", + "resolved": "https://registry.npmmirror.com/miller-rabin/-/miller-rabin-4.0.1.tgz", + "integrity": "sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==", + "dependencies": { + "bn.js": "^4.0.0", + "brorand": "^1.0.1" + }, + "bin": { + "miller-rabin": "bin/miller-rabin" + } + }, + "node_modules/miller-rabin/node_modules/bn.js": { + "version": "4.12.1", + "resolved": "https://registry.npmmirror.com/bn.js/-/bn.js-4.12.1.tgz", + "integrity": "sha512-k8TVBiPkPJT9uHLdOKfFpqcfprwBFOAAXXozRubr7R7PfIuKvQlzcI4M0pALeqXN09vdaMbUdUj+pass+uULAg==" + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmmirror.com/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmmirror.com/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmmirror.com/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==" + }, + "node_modules/minimalistic-crypto-utils": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", + "integrity": "sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==" + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmmirror.com/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmmirror.com/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/mitt": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/mitt/-/mitt-3.0.1.tgz", + "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==" + }, + "node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmmirror.com/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true, + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/mongodb": { + "version": "6.13.1", + "resolved": "https://registry.npmmirror.com/mongodb/-/mongodb-6.13.1.tgz", + "integrity": "sha512-gdq40tX8StmhP6akMp1pPoEVv+9jTYFSrga/g23JxajPAQhH39ysZrHGzQCSd9PEOnuEQEdjIWqxO7ZSwC0w7Q==", + "dependencies": { + "@mongodb-js/saslprep": "^1.1.9", + "bson": "^6.10.3", + "mongodb-connection-string-url": "^3.0.0" + }, + "engines": { + "node": ">=16.20.1" + }, + "peerDependencies": { + "@aws-sdk/credential-providers": "^3.632.0", + "@mongodb-js/zstd": "^1.1.0 || ^2.0.0", + "gcp-metadata": "^5.2.0", + "kerberos": "^2.0.1", + "mongodb-client-encryption": ">=6.0.0 <7", + "snappy": "^7.2.2", + "socks": "^2.7.1" + }, + "peerDependenciesMeta": { + "@aws-sdk/credential-providers": { + "optional": true + }, + "@mongodb-js/zstd": { + "optional": true + }, + "gcp-metadata": { + "optional": true + }, + "kerberos": { + "optional": true + }, + "mongodb-client-encryption": { + "optional": true + }, + "snappy": { + "optional": true + }, + "socks": { + "optional": true + } + } + }, + "node_modules/mongodb-connection-string-url": { + "version": "3.0.2", + "resolved": "https://registry.npmmirror.com/mongodb-connection-string-url/-/mongodb-connection-string-url-3.0.2.tgz", + "integrity": "sha512-rMO7CGo/9BFwyZABcKAWL8UJwH/Kc2x0g72uhDWzG48URRax5TCIcJ7Rc3RZqffZzO/Gwff/jyKwCU9TN8gehA==", + "dependencies": { + "@types/whatwg-url": "^11.0.2", + "whatwg-url": "^14.1.0 || ^13.0.0" + } + }, + "node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmmirror.com/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmmirror.com/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "dev": true + }, + "node_modules/netmask": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/netmask/-/netmask-2.0.2.tgz", + "integrity": "sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/node-cache": { + "version": "5.1.2", + "resolved": "https://registry.npmmirror.com/node-cache/-/node-cache-5.1.2.tgz", + "integrity": "sha512-t1QzWwnk4sjLWaQAS8CHgOJ+RAfmHpxFWmc36IWTiWHQfs0w5JDMBS1b1ZxQteo0vVVuWJvIUKHDkkeK7vIGCg==", + "dependencies": { + "clone": "2.x" + }, + "engines": { + "node": ">= 8.0.0" + } + }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmmirror.com/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/node-fetch/node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmmirror.com/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, + "node_modules/node-fetch/node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, + "node_modules/node-fetch/node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/node-releases": { + "version": "2.0.19", + "resolved": "https://registry.npmmirror.com/node-releases/-/node-releases-2.0.19.tgz", + "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", + "dev": true + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmmirror.com/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, + "node_modules/nwsapi": { + "version": "2.2.16", + "resolved": "https://registry.npmmirror.com/nwsapi/-/nwsapi-2.2.16.tgz", + "integrity": "sha512-F1I/bimDpj3ncaNDhfyMWuFqmQDBwDB0Fogc2qpL3BWvkQteFD/8BzWuIRl83rq0DXfm8SGt/HFhLXZyljTXcQ==" + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmmirror.com/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-is": { + "version": "1.1.6", + "resolved": "https://registry.npmmirror.com/object-is/-/object-is-1.1.6.tgz", + "integrity": "sha512-F8cZ+KfGlSGi09lJT7/Nd6KJZ9ygtvYC0/UYYLI9nmQKLMnydpB9yvbv9K1uSkEu7FU9vYPmVwLg328tX+ot3Q==", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.7", + "resolved": "https://registry.npmmirror.com/object.assign/-/object.assign-4.1.7.tgz", + "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0", + "has-symbols": "^1.1.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmmirror.com/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmmirror.com/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/os-browserify": { + "version": "0.3.0", + "resolved": "https://registry.npmmirror.com/os-browserify/-/os-browserify-0.3.0.tgz", + "integrity": "sha512-gjcpUc3clBf9+210TRaDWbf+rZZZEshZ+DlXMRCeAjp0xhTrnQsKHypIy1J3d5hKdUzj69t708EHtU8P6bUn0A==" + }, + "node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmmirror.com/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmmirror.com/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmmirror.com/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/pac-proxy-agent": { + "version": "7.1.0", + "resolved": "https://registry.npmmirror.com/pac-proxy-agent/-/pac-proxy-agent-7.1.0.tgz", + "integrity": "sha512-Z5FnLVVZSnX7WjBg0mhDtydeRZ1xMcATZThjySQUHqr+0ksP8kqaw23fNKkaaN/Z8gwLUs/W7xdl0I75eP2Xyw==", + "dependencies": { + "@tootallnate/quickjs-emscripten": "^0.23.0", + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "get-uri": "^6.0.1", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.6", + "pac-resolver": "^7.0.1", + "socks-proxy-agent": "^8.0.5" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/pac-proxy-agent/node_modules/debug": { + "version": "4.4.0", + "resolved": "https://registry.npmmirror.com/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/pac-proxy-agent/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/pac-resolver": { + "version": "7.0.1", + "resolved": "https://registry.npmmirror.com/pac-resolver/-/pac-resolver-7.0.1.tgz", + "integrity": "sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg==", + "dependencies": { + "degenerator": "^5.0.0", + "netmask": "^2.0.2" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/pako": { + "version": "1.0.11", + "resolved": "https://registry.npmmirror.com/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==" + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-asn1": { + "version": "5.1.7", + "resolved": "https://registry.npmmirror.com/parse-asn1/-/parse-asn1-5.1.7.tgz", + "integrity": "sha512-CTM5kuWR3sx9IFamcl5ErfPl6ea/N8IYwiJ+vpeB2g+1iknv7zBl5uPwbMbRVznRVbrNY6lGuDoE5b30grmbqg==", + "dependencies": { + "asn1.js": "^4.10.1", + "browserify-aes": "^1.2.0", + "evp_bytestokey": "^1.0.3", + "hash-base": "~3.0", + "pbkdf2": "^3.1.2", + "safe-buffer": "^5.2.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmmirror.com/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parse5": { + "version": "7.2.1", + "resolved": "https://registry.npmmirror.com/parse5/-/parse5-7.2.1.tgz", + "integrity": "sha512-BuBYQYlv1ckiPdQi/ohiivi9Sagc9JG+Ozs0r7b/0iK3sKmrb0b9FdWdBbOdx6hBCM/F9Ir82ofnBhtZOjCRPQ==", + "dependencies": { + "entities": "^4.5.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5-htmlparser2-tree-adapter": { + "version": "7.1.0", + "resolved": "https://registry.npmmirror.com/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.1.0.tgz", + "integrity": "sha512-ruw5xyKs6lrpo9x9rCZqZZnIUntICjQAd0Wsmp396Ul9lN/h+ifgVV1x1gZHi8euej6wTfpqX8j+BFQxF0NS/g==", + "dependencies": { + "domhandler": "^5.0.3", + "parse5": "^7.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5-parser-stream": { + "version": "7.1.2", + "resolved": "https://registry.npmmirror.com/parse5-parser-stream/-/parse5-parser-stream-7.1.2.tgz", + "integrity": "sha512-JyeQc9iwFLn5TbvvqACIF/VXG6abODeB3Fwmv/TGdLk2LfbWkaySGY72at4+Ty7EkPZj854u4CrICqNk2qIbow==", + "dependencies": { + "parse5": "^7.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmmirror.com/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-browserify": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/path-browserify/-/path-browserify-1.0.1.tgz", + "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==" + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmmirror.com/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmmirror.com/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "node_modules/path-to-regexp": { + "version": "0.1.12", + "resolved": "https://registry.npmmirror.com/path-to-regexp/-/path-to-regexp-0.1.12.tgz", + "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==" + }, + "node_modules/pbkdf2": { + "version": "3.1.2", + "resolved": "https://registry.npmmirror.com/pbkdf2/-/pbkdf2-3.1.2.tgz", + "integrity": "sha512-iuh7L6jA7JEGu2WxDwtQP1ddOpaJNC4KlDEFfdQajSGgGPNi4OyDc2R7QnbY2bR9QjBVGwgvTdNJZoE7RaxUMA==", + "dependencies": { + "create-hash": "^1.1.2", + "create-hmac": "^1.1.4", + "ripemd160": "^2.0.1", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" + }, + "engines": { + "node": ">=0.12" + } + }, + "node_modules/pend": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/pend/-/pend-1.2.0.tgz", + "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmmirror.com/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/possible-typed-array-names": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", + "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" + }, + "node_modules/progress": { + "version": "2.0.3", + "resolved": "https://registry.npmmirror.com/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmmirror.com/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/proxy-agent": { + "version": "6.5.0", + "resolved": "https://registry.npmmirror.com/proxy-agent/-/proxy-agent-6.5.0.tgz", + "integrity": "sha512-TmatMXdr2KlRiA2CyDu8GqR8EjahTG3aY3nXjdzFyoZbmB8hrBsTyMezhULIXKnC0jpfjlmiZ3+EaCzoInSu/A==", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "http-proxy-agent": "^7.0.1", + "https-proxy-agent": "^7.0.6", + "lru-cache": "^7.14.1", + "pac-proxy-agent": "^7.1.0", + "proxy-from-env": "^1.1.0", + "socks-proxy-agent": "^8.0.5" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/proxy-agent/node_modules/debug": { + "version": "4.4.0", + "resolved": "https://registry.npmmirror.com/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/proxy-agent/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, + "node_modules/public-encrypt": { + "version": "4.0.3", + "resolved": "https://registry.npmmirror.com/public-encrypt/-/public-encrypt-4.0.3.tgz", + "integrity": "sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q==", + "dependencies": { + "bn.js": "^4.1.0", + "browserify-rsa": "^4.0.0", + "create-hash": "^1.1.0", + "parse-asn1": "^5.0.0", + "randombytes": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "node_modules/public-encrypt/node_modules/bn.js": { + "version": "4.12.1", + "resolved": "https://registry.npmmirror.com/bn.js/-/bn.js-4.12.1.tgz", + "integrity": "sha512-k8TVBiPkPJT9uHLdOKfFpqcfprwBFOAAXXozRubr7R7PfIuKvQlzcI4M0pALeqXN09vdaMbUdUj+pass+uULAg==" + }, + "node_modules/pump": { + "version": "3.0.2", + "resolved": "https://registry.npmmirror.com/pump/-/pump-3.0.2.tgz", + "integrity": "sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmmirror.com/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "engines": { + "node": ">=6" + } + }, + "node_modules/puppeteer": { + "version": "24.2.1", + "resolved": "https://registry.npmmirror.com/puppeteer/-/puppeteer-24.2.1.tgz", + "integrity": "sha512-Euno62ou0cd0dTkOYTNioSOsFF4VpSnz4ldD38hi9ov9xCNtr8DbhmoJRUx+V9OuPgecueZbKOohRrnrhkbg3Q==", + "hasInstallScript": true, + "dependencies": { + "@puppeteer/browsers": "2.7.1", + "chromium-bidi": "1.3.0", + "cosmiconfig": "^9.0.0", + "devtools-protocol": "0.0.1402036", + "puppeteer-core": "24.2.1", + "typed-query-selector": "^2.12.0" + }, + "bin": { + "puppeteer": "lib/cjs/puppeteer/node/cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/puppeteer-cluster": { + "version": "0.24.0", + "resolved": "https://registry.npmmirror.com/puppeteer-cluster/-/puppeteer-cluster-0.24.0.tgz", + "integrity": "sha512-zHPoNsrwkFLKFtgJJv2aC13EbMASQsE048uZd7CyikEXcl+sc1Nf6PMFb9kMoZI7/zMYxvuP658o2mw079ZZyQ==", + "dependencies": { + "debug": "^4.3.4" + }, + "peerDependencies": { + "puppeteer": ">=22.0.0" + } + }, + "node_modules/puppeteer-cluster/node_modules/debug": { + "version": "4.4.0", + "resolved": "https://registry.npmmirror.com/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/puppeteer-cluster/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/puppeteer-core": { + "version": "24.2.1", + "resolved": "https://registry.npmmirror.com/puppeteer-core/-/puppeteer-core-24.2.1.tgz", + "integrity": "sha512-bCypUh3WXzETafv1TCFAjIUnI8BiQ/d+XvEfEXDLcIMm9CAvROqnBmbt79yBjwasoDZsgfXnUmIJU7Y27AalVQ==", + "dependencies": { + "@puppeteer/browsers": "2.7.1", + "chromium-bidi": "1.3.0", + "debug": "^4.4.0", + "devtools-protocol": "0.0.1402036", + "typed-query-selector": "^2.12.0", + "ws": "^8.18.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/puppeteer-core/node_modules/debug": { + "version": "4.4.0", + "resolved": "https://registry.npmmirror.com/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/puppeteer-core/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/qs": { + "version": "6.13.0", + "resolved": "https://registry.npmmirror.com/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "dependencies": { + "side-channel": "^1.0.6" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/querystring-es3": { + "version": "0.2.1", + "resolved": "https://registry.npmmirror.com/querystring-es3/-/querystring-es3-0.2.1.tgz", + "integrity": "sha512-773xhDQnZBMFobEiztv8LIl70ch5MSF/jUQVlhwFyBILqq96anmoctVIYz+ZRp0qbCKATTn6ev02M3r7Ga5vqA==", + "engines": { + "node": ">=0.4.x" + } + }, + "node_modules/random-seed": { + "version": "0.3.0", + "resolved": "https://registry.npmmirror.com/random-seed/-/random-seed-0.3.0.tgz", + "integrity": "sha512-y13xtn3kcTlLub3HKWXxJNeC2qK4mB59evwZ5EkeRlolx+Bp2ztF7LbcZmyCnOqlHQrLnfuNbi1sVmm9lPDlDA==", + "dependencies": { + "json-stringify-safe": "^5.0.1" + }, + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/random-useragent": { + "version": "0.5.0", + "resolved": "https://registry.npmmirror.com/random-useragent/-/random-useragent-0.5.0.tgz", + "integrity": "sha512-FUMkqVdZeoSff5tErNL3FFGYXElDWZ1bEuedhm5u9MdCFwANriJWbHvDRYrLTOzp/fBsBGu5J1cWtDgifa97aQ==", + "dependencies": { + "random-seed": "^0.3.0" + } + }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, + "node_modules/randomfill": { + "version": "1.0.4", + "resolved": "https://registry.npmmirror.com/randomfill/-/randomfill-1.0.4.tgz", + "integrity": "sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==", + "dependencies": { + "randombytes": "^2.0.5", + "safe-buffer": "^5.1.0" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmmirror.com/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.2", + "resolved": "https://registry.npmmirror.com/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmmirror.com/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/readable-stream/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmmirror.com/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "node_modules/readable-stream/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmmirror.com/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/rechoir": { + "version": "0.8.0", + "resolved": "https://registry.npmmirror.com/rechoir/-/rechoir-0.8.0.tgz", + "integrity": "sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ==", + "dev": true, + "dependencies": { + "resolve": "^1.20.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmmirror.com/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve": { + "version": "1.22.10", + "resolved": "https://registry.npmmirror.com/resolve/-/resolve-1.22.10.tgz", + "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", + "dev": true, + "dependencies": { + "is-core-module": "^2.16.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "dev": true, + "dependencies": { + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-cwd/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "engines": { + "node": ">=4" + } + }, + "node_modules/rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmmirror.com/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + } + }, + "node_modules/ripemd160": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/ripemd160/-/ripemd160-2.0.2.tgz", + "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==", + "dependencies": { + "hash-base": "^3.0.0", + "inherits": "^2.0.1" + } + }, + "node_modules/rrweb-cssom": { + "version": "0.8.0", + "resolved": "https://registry.npmmirror.com/rrweb-cssom/-/rrweb-cssom-0.8.0.tgz", + "integrity": "sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==" + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmmirror.com/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/safe-regex-test": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/safe-regex-test/-/safe-regex-test-1.1.0.tgz", + "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-regex": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmmirror.com/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "node_modules/saxes": { + "version": "6.0.0", + "resolved": "https://registry.npmmirror.com/saxes/-/saxes-6.0.0.tgz", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=v12.22.7" + } + }, + "node_modules/schema-utils": { + "version": "4.3.0", + "resolved": "https://registry.npmmirror.com/schema-utils/-/schema-utils-4.3.0.tgz", + "integrity": "sha512-Gf9qqc58SpCA/xdziiHz35F4GNIWYWZrEshUc/G/r5BnLph6xpKuLeoJoQuj5WfBIx/eQLf+hmVPYHaxJu7V2g==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.9.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.1.0" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/semver": { + "version": "7.7.1", + "resolved": "https://registry.npmmirror.com/semver/-/semver-7.7.1.tgz", + "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/send": { + "version": "0.19.0", + "resolved": "https://registry.npmmirror.com/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/send/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/serialize-javascript": { + "version": "6.0.2", + "resolved": "https://registry.npmmirror.com/serialize-javascript/-/serialize-javascript-6.0.2.tgz", + "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", + "dev": true, + "dependencies": { + "randombytes": "^2.1.0" + } + }, + "node_modules/serve-static": { + "version": "1.16.2", + "resolved": "https://registry.npmmirror.com/serve-static/-/serve-static-1.16.2.tgz", + "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", + "dependencies": { + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.19.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmmirror.com/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" + }, + "node_modules/sha.js": { + "version": "2.4.11", + "resolved": "https://registry.npmmirror.com/sha.js/-/sha.js-2.4.11.tgz", + "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", + "dependencies": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + }, + "bin": { + "sha.js": "bin.js" + } + }, + "node_modules/shallow-clone": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/shallow-clone/-/shallow-clone-3.0.1.tgz", + "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", + "dev": true, + "dependencies": { + "kind-of": "^6.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmmirror.com/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks": { + "version": "2.8.4", + "resolved": "https://registry.npmmirror.com/socks/-/socks-2.8.4.tgz", + "integrity": "sha512-D3YaD0aRxR3mEcqnidIs7ReYJFVzWdd6fXJYUM8ixcQcJRGTka/b3saV0KflYhyVJXKhb947GndU35SxYNResQ==", + "dependencies": { + "ip-address": "^9.0.5", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks-proxy-agent": { + "version": "8.0.5", + "resolved": "https://registry.npmmirror.com/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz", + "integrity": "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "socks": "^2.8.3" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/socks-proxy-agent/node_modules/debug": { + "version": "4.4.0", + "resolved": "https://registry.npmmirror.com/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/socks-proxy-agent/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmmirror.com/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "devOptional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmmirror.com/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/sparse-bitfield": { + "version": "3.0.3", + "resolved": "https://registry.npmmirror.com/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz", + "integrity": "sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==", + "dependencies": { + "memory-pager": "^1.0.2" + } + }, + "node_modules/spider": { + "resolved": "", + "link": true + }, + "node_modules/sprintf-js": { + "version": "1.1.3", + "resolved": "https://registry.npmmirror.com/sprintf-js/-/sprintf-js-1.1.3.tgz", + "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==" + }, + "node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/stream-browserify": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/stream-browserify/-/stream-browserify-3.0.0.tgz", + "integrity": "sha512-H73RAHsVBapbim0tU2JwwOiXUj+fikfiaoYAKHF3VJfA0pe2BCzkhAHBlLG6REzE+2WNZcxOXjK7lkso+9euLA==", + "dependencies": { + "inherits": "~2.0.4", + "readable-stream": "^3.5.0" + } + }, + "node_modules/stream-browserify/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmmirror.com/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/stream-http": { + "version": "3.2.0", + "resolved": "https://registry.npmmirror.com/stream-http/-/stream-http-3.2.0.tgz", + "integrity": "sha512-Oq1bLqisTyK3TSCXpPbT4sdeYNdmyZJv1LxpEm2vu1ZhK89kSE5YXwZc3cWk0MagGaKriBh9mCFbVGtO+vY29A==", + "dependencies": { + "builtin-status-codes": "^3.0.0", + "inherits": "^2.0.4", + "readable-stream": "^3.6.0", + "xtend": "^4.0.2" + } + }, + "node_modules/stream-http/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmmirror.com/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/streamx": { + "version": "2.22.0", + "resolved": "https://registry.npmmirror.com/streamx/-/streamx-2.22.0.tgz", + "integrity": "sha512-sLh1evHOzBy/iWRiR6d1zRcLao4gGZr3C1kzNz4fopCOKJb6xD9ub8Mpi9Mr1R6id5o43S+d93fI48UC5uM9aw==", + "dependencies": { + "fast-fifo": "^1.3.2", + "text-decoder": "^1.1.0" + }, + "optionalDependencies": { + "bare-events": "^2.2.0" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmmirror.com/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmmirror.com/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmmirror.com/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmmirror.com/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==" + }, + "node_modules/tapable": { + "version": "2.2.1", + "resolved": "https://registry.npmmirror.com/tapable/-/tapable-2.2.1.tgz", + "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/tar-fs": { + "version": "3.0.8", + "resolved": "https://registry.npmmirror.com/tar-fs/-/tar-fs-3.0.8.tgz", + "integrity": "sha512-ZoROL70jptorGAlgAYiLoBLItEKw/fUxg9BSYK/dF/GAGYFJOJJJMvjPAKDJraCXFwadD456FCuvLWgfhMsPwg==", + "dependencies": { + "pump": "^3.0.0", + "tar-stream": "^3.1.5" + }, + "optionalDependencies": { + "bare-fs": "^4.0.1", + "bare-path": "^3.0.0" + } + }, + "node_modules/tar-stream": { + "version": "3.1.7", + "resolved": "https://registry.npmmirror.com/tar-stream/-/tar-stream-3.1.7.tgz", + "integrity": "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==", + "dependencies": { + "b4a": "^1.6.4", + "fast-fifo": "^1.2.0", + "streamx": "^2.15.0" + } + }, + "node_modules/terser": { + "version": "5.39.0", + "resolved": "https://registry.npmmirror.com/terser/-/terser-5.39.0.tgz", + "integrity": "sha512-LBAhFyLho16harJoWMg/nZsQYgTrg5jXOn2nCYjRUcZZEdE3qa2zb8QEDRUGVZBW4rlazf2fxkg8tztybTaqWw==", + "dev": true, + "dependencies": { + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.8.2", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/terser-webpack-plugin": { + "version": "5.3.11", + "resolved": "https://registry.npmmirror.com/terser-webpack-plugin/-/terser-webpack-plugin-5.3.11.tgz", + "integrity": "sha512-RVCsMfuD0+cTt3EwX8hSl2Ks56EbFHWmhluwcqoPKtBnfjiT6olaq7PRIRfhyU8nnC2MrnDrBLfrD/RGE+cVXQ==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.25", + "jest-worker": "^27.4.5", + "schema-utils": "^4.3.0", + "serialize-javascript": "^6.0.2", + "terser": "^5.31.1" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.1.0" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "uglify-js": { + "optional": true + } + } + }, + "node_modules/terser-webpack-plugin/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmmirror.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/text-decoder": { + "version": "1.2.3", + "resolved": "https://registry.npmmirror.com/text-decoder/-/text-decoder-1.2.3.tgz", + "integrity": "sha512-3/o9z3X0X0fTupwsYvR03pJ/DjWuqqrfwBgTQzdWDiQSm9KitAyz/9WqsT2JQW7KV2m+bC2ol/zqpW37NHxLaA==", + "dependencies": { + "b4a": "^1.6.4" + } + }, + "node_modules/tldts": { + "version": "6.1.77", + "resolved": "https://registry.npmmirror.com/tldts/-/tldts-6.1.77.tgz", + "integrity": "sha512-lBpoWgy+kYmuXWQ83+R7LlJCnsd9YW8DGpZSHhrMl4b8Ly/1vzOie3OdtmUJDkKxcgRGOehDu5btKkty+JEe+g==", + "dependencies": { + "tldts-core": "^6.1.77" + }, + "bin": { + "tldts": "bin/cli.js" + } + }, + "node_modules/tldts-core": { + "version": "6.1.77", + "resolved": "https://registry.npmmirror.com/tldts-core/-/tldts-core-6.1.77.tgz", + "integrity": "sha512-bCaqm24FPk8OgBkM0u/SrEWJgHnhBWYqeBo6yUmcZJDCHt/IfyWBb+14CXdGi4RInMv4v7eUAin15W0DoA+Ytg==" + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmmirror.com/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/tough-cookie": { + "version": "5.1.1", + "resolved": "https://registry.npmmirror.com/tough-cookie/-/tough-cookie-5.1.1.tgz", + "integrity": "sha512-Ek7HndSVkp10hmHP9V4qZO1u+pn1RU5sI0Fw+jCU3lyvuMZcgqsNgc6CmJJZyByK4Vm/qotGRJlfgAX8q+4JiA==", + "dependencies": { + "tldts": "^6.1.32" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/tr46": { + "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/tr46/-/tr46-5.0.0.tgz", + "integrity": "sha512-tk2G5R2KRwBd+ZN0zaEXpmzdKyOYksXwywulIX95MBODjSzMIuQnQ3m8JxgbhnL1LeVo7lqQKsYa1O3Htl7K5g==", + "dependencies": { + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/tree-kill": { + "version": "1.2.2", + "resolved": "https://registry.npmmirror.com/tree-kill/-/tree-kill-1.2.2.tgz", + "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", + "dev": true, + "bin": { + "tree-kill": "cli.js" + } + }, + "node_modules/ts-loader": { + "version": "9.5.2", + "resolved": "https://registry.npmmirror.com/ts-loader/-/ts-loader-9.5.2.tgz", + "integrity": "sha512-Qo4piXvOTWcMGIgRiuFa6nHNm+54HbYaZCKqc9eeZCLRy3XqafQgwX2F7mofrbJG3g7EEb+lkiR+z2Lic2s3Zw==", + "dev": true, + "dependencies": { + "chalk": "^4.1.0", + "enhanced-resolve": "^5.0.0", + "micromatch": "^4.0.0", + "semver": "^7.3.4", + "source-map": "^0.7.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "typescript": "*", + "webpack": "^5.0.0" + } + }, + "node_modules/ts-loader/node_modules/source-map": { + "version": "0.7.4", + "resolved": "https://registry.npmmirror.com/source-map/-/source-map-0.7.4.tgz", + "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/ts-node": { + "version": "10.9.2", + "resolved": "https://registry.npmmirror.com/ts-node/-/ts-node-10.9.2.tgz", + "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", + "dev": true, + "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, + "node_modules/ts-node-dev": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/ts-node-dev/-/ts-node-dev-2.0.0.tgz", + "integrity": "sha512-ywMrhCfH6M75yftYvrvNarLEY+SUXtUvU8/0Z6llrHQVBx12GiFk5sStF8UdfE/yfzk9IAq7O5EEbTQsxlBI8w==", + "dev": true, + "dependencies": { + "chokidar": "^3.5.1", + "dynamic-dedupe": "^0.3.0", + "minimist": "^1.2.6", + "mkdirp": "^1.0.4", + "resolve": "^1.0.0", + "rimraf": "^2.6.1", + "source-map-support": "^0.5.12", + "tree-kill": "^1.2.2", + "ts-node": "^10.4.0", + "tsconfig": "^7.0.0" + }, + "bin": { + "ts-node-dev": "lib/bin.js", + "tsnd": "lib/bin.js" + }, + "engines": { + "node": ">=0.8.0" + }, + "peerDependencies": { + "node-notifier": "*", + "typescript": "*" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/tsconfig": { + "version": "7.0.0", + "resolved": "https://registry.npmmirror.com/tsconfig/-/tsconfig-7.0.0.tgz", + "integrity": "sha512-vZXmzPrL+EmC4T/4rVlT2jNVMWCi/O4DIiSj3UHg1OE5kCKbk4mfrXc6dZksLgRM/TZlKnousKH9bbTazUWRRw==", + "dev": true, + "dependencies": { + "@types/strip-bom": "^3.0.0", + "@types/strip-json-comments": "0.0.30", + "strip-bom": "^3.0.0", + "strip-json-comments": "^2.0.0" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmmirror.com/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + }, + "node_modules/turndown": { + "version": "7.2.0", + "resolved": "https://registry.npmmirror.com/turndown/-/turndown-7.2.0.tgz", + "integrity": "sha512-eCZGBN4nNNqM9Owkv9HAtWRYfLA4h909E/WGAWWBpmB275ehNhZyk87/Tpvjbp0jjNl9XwCsbe6bm6CqFsgD+A==", + "dependencies": { + "@mixmark-io/domino": "^2.2.0" + } + }, + "node_modules/turndown-plugin-gfm": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/turndown-plugin-gfm/-/turndown-plugin-gfm-1.0.2.tgz", + "integrity": "sha512-vwz9tfvF7XN/jE0dGoBei3FXWuvll78ohzCZQuOb+ZjWrs3a0XhQVomJEb2Qh4VHTPNRO4GPZh0V7VRbiWwkRg==" + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmmirror.com/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/typed-query-selector": { + "version": "2.12.0", + "resolved": "https://registry.npmmirror.com/typed-query-selector/-/typed-query-selector-2.12.0.tgz", + "integrity": "sha512-SbklCd1F0EiZOyPiW192rrHZzZ5sBijB6xM+cpmrwDqObvdtunOHHIk9fCGsoK5JVIYXoyEp4iEdE3upFH3PAg==" + }, + "node_modules/typescript": { + "version": "5.7.3", + "resolved": "https://registry.npmmirror.com/typescript/-/typescript-5.7.3.tgz", + "integrity": "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==", + "devOptional": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici": { + "version": "6.21.1", + "resolved": "https://registry.npmmirror.com/undici/-/undici-6.21.1.tgz", + "integrity": "sha512-q/1rj5D0/zayJB2FraXdaWxbhWiNKDvu8naDT2dl1yTlvJp4BLtOcp2a5BvgGNQpYYJzau7tf1WgKv3b+7mqpQ==", + "engines": { + "node": ">=18.17" + } + }, + "node_modules/undici-types": { + "version": "6.20.0", + "resolved": "https://registry.npmmirror.com/undici-types/-/undici-types-6.20.0.tgz", + "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==" + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/update-browserslist-db/-/update-browserslist-db-1.1.2.tgz", + "integrity": "sha512-PPypAm5qvlD7XMZC3BujecnaOxwhrtoFR+Dqkk5Aa/6DssiH0ibKoketaj9w8LP7Bont1rYeoV5plxD7RTEPRg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/url": { + "version": "0.11.4", + "resolved": "https://registry.npmmirror.com/url/-/url-0.11.4.tgz", + "integrity": "sha512-oCwdVC7mTuWiPyjLUz/COz5TLk6wgp0RCsN+wHZ2Ekneac9w8uuV0njcbbie2ME+Vs+d6duwmYuR3HgQXs1fOg==", + "dependencies": { + "punycode": "^1.4.1", + "qs": "^6.12.3" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/url/node_modules/punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmmirror.com/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==" + }, + "node_modules/user-agents": { + "version": "1.1.454", + "resolved": "https://registry.npmmirror.com/user-agents/-/user-agents-1.1.454.tgz", + "integrity": "sha512-MdfDDPTeCzFBEWoRYhnpqVIkuO7WXff5GmDogNWgLqx00yHxxSJaEMS9cN3C049+b22I4FncPNodBksKLuGFeg==", + "dependencies": { + "lodash.clonedeep": "^4.5.0" + } + }, + "node_modules/util": { + "version": "0.12.5", + "resolved": "https://registry.npmmirror.com/util/-/util-0.12.5.tgz", + "integrity": "sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==", + "dependencies": { + "inherits": "^2.0.3", + "is-arguments": "^1.0.4", + "is-generator-function": "^1.0.7", + "is-typed-array": "^1.1.3", + "which-typed-array": "^1.1.2" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/vm-browserify": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/vm-browserify/-/vm-browserify-1.1.2.tgz", + "integrity": "sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==" + }, + "node_modules/w3c-xmlserializer": { + "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", + "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==", + "dependencies": { + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/watchpack": { + "version": "2.4.2", + "resolved": "https://registry.npmmirror.com/watchpack/-/watchpack-2.4.2.tgz", + "integrity": "sha512-TnbFSbcOCcDgjZ4piURLCbJ3nJhznVh9kw6F6iokjiFPl8ONxe9A6nMDVXDiNbrSfLILs6vB07F7wLBrwPYzJw==", + "dev": true, + "dependencies": { + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.1.2" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmmirror.com/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "engines": { + "node": ">=12" + } + }, + "node_modules/webpack": { + "version": "5.98.0", + "resolved": "https://registry.npmmirror.com/webpack/-/webpack-5.98.0.tgz", + "integrity": "sha512-UFynvx+gM44Gv9qFgj0acCQK2VE1CtdfwFdimkapco3hlPCJ/zeq73n2yVKimVbtm+TnApIugGhLJnkU6gjYXA==", + "dev": true, + "dependencies": { + "@types/eslint-scope": "^3.7.7", + "@types/estree": "^1.0.6", + "@webassemblyjs/ast": "^1.14.1", + "@webassemblyjs/wasm-edit": "^1.14.1", + "@webassemblyjs/wasm-parser": "^1.14.1", + "acorn": "^8.14.0", + "browserslist": "^4.24.0", + "chrome-trace-event": "^1.0.2", + "enhanced-resolve": "^5.17.1", + "es-module-lexer": "^1.2.1", + "eslint-scope": "5.1.1", + "events": "^3.2.0", + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.2.11", + "json-parse-even-better-errors": "^2.3.1", + "loader-runner": "^4.2.0", + "mime-types": "^2.1.27", + "neo-async": "^2.6.2", + "schema-utils": "^4.3.0", + "tapable": "^2.1.1", + "terser-webpack-plugin": "^5.3.11", + "watchpack": "^2.4.1", + "webpack-sources": "^3.2.3" + }, + "bin": { + "webpack": "bin/webpack.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependenciesMeta": { + "webpack-cli": { + "optional": true + } + } + }, + "node_modules/webpack-cli": { + "version": "6.0.1", + "resolved": "https://registry.npmmirror.com/webpack-cli/-/webpack-cli-6.0.1.tgz", + "integrity": "sha512-MfwFQ6SfwinsUVi0rNJm7rHZ31GyTcpVE5pgVA3hwFRb7COD4TzjUUwhGWKfO50+xdc2MQPuEBBJoqIMGt3JDw==", + "dev": true, + "dependencies": { + "@discoveryjs/json-ext": "^0.6.1", + "@webpack-cli/configtest": "^3.0.1", + "@webpack-cli/info": "^3.0.1", + "@webpack-cli/serve": "^3.0.1", + "colorette": "^2.0.14", + "commander": "^12.1.0", + "cross-spawn": "^7.0.3", + "envinfo": "^7.14.0", + "fastest-levenshtein": "^1.0.12", + "import-local": "^3.0.2", + "interpret": "^3.1.1", + "rechoir": "^0.8.0", + "webpack-merge": "^6.0.1" + }, + "bin": { + "webpack-cli": "bin/cli.js" + }, + "engines": { + "node": ">=18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.82.0" + }, + "peerDependenciesMeta": { + "webpack-bundle-analyzer": { + "optional": true + }, + "webpack-dev-server": { + "optional": true + } + } + }, + "node_modules/webpack-cli/node_modules/commander": { + "version": "12.1.0", + "resolved": "https://registry.npmmirror.com/commander/-/commander-12.1.0.tgz", + "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", + "dev": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/webpack-merge": { + "version": "6.0.1", + "resolved": "https://registry.npmmirror.com/webpack-merge/-/webpack-merge-6.0.1.tgz", + "integrity": "sha512-hXXvrjtx2PLYx4qruKl+kyRSLc52V+cCvMxRjmKwoA+CBbbF5GfIBtR6kCvl0fYGqTUPKB+1ktVmTHqMOzgCBg==", + "dev": true, + "dependencies": { + "clone-deep": "^4.0.1", + "flat": "^5.0.2", + "wildcard": "^2.0.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/webpack-node-externals": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/webpack-node-externals/-/webpack-node-externals-3.0.0.tgz", + "integrity": "sha512-LnL6Z3GGDPht/AigwRh2dvL9PQPFQ8skEpVrWZXLWBYmqcaojHNN0onvHzie6rq7EWKrrBfPYqNEzTJgiwEQDQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/webpack-sources": { + "version": "3.2.3", + "resolved": "https://registry.npmmirror.com/webpack-sources/-/webpack-sources-3.2.3.tgz", + "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==", + "dev": true, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/whatwg-encoding": { + "version": "3.1.1", + "resolved": "https://registry.npmmirror.com/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", + "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", + "dependencies": { + "iconv-lite": "0.6.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/whatwg-encoding/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmmirror.com/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/whatwg-mimetype": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", + "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", + "engines": { + "node": ">=18" + } + }, + "node_modules/whatwg-url": { + "version": "14.1.1", + "resolved": "https://registry.npmmirror.com/whatwg-url/-/whatwg-url-14.1.1.tgz", + "integrity": "sha512-mDGf9diDad/giZ/Sm9Xi2YcyzaFpbdLpJPr+E9fSkyQ7KpQD4SdFcugkRQYzhmfI4KeV4Qpnn2sKPdo+kmsgRQ==", + "dependencies": { + "tr46": "^5.0.0", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.18", + "resolved": "https://registry.npmmirror.com/which-typed-array/-/which-typed-array-1.1.18.tgz", + "integrity": "sha512-qEcY+KJYlWyLH9vNbsr6/5j59AXk5ni5aakf8ldzBvGde6Iz4sxZGkJyWSAueTG7QhOvNRYb1lDdFmL5Td0QKA==", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/wildcard": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/wildcard/-/wildcard-2.0.1.tgz", + "integrity": "sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ==", + "dev": true + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmmirror.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" + }, + "node_modules/ws": { + "version": "8.18.0", + "resolved": "https://registry.npmmirror.com/ws/-/ws-8.18.0.tgz", + "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xml-name-validator": { + "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/xml-name-validator/-/xml-name-validator-5.0.0.tgz", + "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==", + "engines": { + "node": ">=18" + } + }, + "node_modules/xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmmirror.com/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==" + }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmmirror.com/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "engines": { + "node": ">=0.4" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmmirror.com/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmmirror.com/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmmirror.com/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "engines": { + "node": ">=12" + } + }, + "node_modules/yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmmirror.com/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", + "dependencies": { + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.1.0" + } + }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmmirror.com/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/zod": { + "version": "3.24.2", + "resolved": "https://registry.npmmirror.com/zod/-/zod-3.24.2.tgz", + "integrity": "sha512-lY7CDW43ECgW9u1TcT3IoXHflywfVqDYze4waEz812jR/bZ8FHDsl7pFQoSZTz5N+2NqRXs8GBwnAwo3ZNxqhQ==", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + } + } +} diff --git a/plugins/webcrawler/SPIDER/package.json b/plugins/webcrawler/SPIDER/package.json new file mode 100644 index 000000000000..d1c1dcefb397 --- /dev/null +++ b/plugins/webcrawler/SPIDER/package.json @@ -0,0 +1,62 @@ +{ + "name": "spider", + "version": "1.0.0", + "description": "", + "main": "/dist/index.ts", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1", + "start": "ts-node src/index.ts", + "build": "webpack", + "dev": "ts-node-dev --respawn src/index.ts" + }, + "keywords": [], + "author": "", + "license": "ISC", + "dependencies": { + "@types/node-fetch": "^2.6.12", + "assert": "^2.1.0", + "axios": "^1.7.9", + "body-parser": "^1.20.3", + "browserify-zlib": "^0.2.0", + "buffer": "^6.0.3", + "cheerio": "^1.0.0", + "crypto-browserify": "^3.12.1", + "dotenv": "^16.4.7", + "express": "^4.21.2", + "https-proxy-agent": "^7.0.6", + "jsdom": "^26.0.0", + "mongodb": "^6.13.1", + "node-cache": "^5.1.2", + "node-fetch": "^2.7.0", + "os-browserify": "^0.3.0", + "path-browserify": "^1.0.1", + "puppeteer": "^24.2.1", + "puppeteer-cluster": "^0.24.0", + "querystring-es3": "^0.2.1", + "random-useragent": "^0.5.0", + "spider": "file:", + "stream-browserify": "^3.0.0", + "stream-http": "^3.2.0", + "string_decoder": "^1.3.0", + "turndown": "^7.2.0", + "turndown-plugin-gfm": "^1.0.2", + "url": "^0.11.4", + "user-agents": "^1.1.454", + "util": "^0.12.5", + "vm-browserify": "^1.1.2" + }, + "devDependencies": { + "@types/body-parser": "^1.19.5", + "@types/express": "^5.0.0", + "@types/jsdom": "^21.1.7", + "@types/node": "^22.13.4", + "@types/random-useragent": "^0.3.3", + "@types/user-agents": "^1.0.4", + "ts-loader": "^9.5.2", + "ts-node-dev": "^2.0.0", + "typescript": "^5.7.3", + "webpack": "^5.98.0", + "webpack-cli": "^6.0.1", + "webpack-node-externals": "^3.0.0" + } +} diff --git a/plugins/webcrawler/SPIDER/src/controllers/quickfetchController.ts b/plugins/webcrawler/SPIDER/src/controllers/quickfetchController.ts new file mode 100644 index 000000000000..ce5230fc97a2 --- /dev/null +++ b/plugins/webcrawler/SPIDER/src/controllers/quickfetchController.ts @@ -0,0 +1,60 @@ +import { Request, Response } from 'express'; +import fetch from 'node-fetch'; +import dotenv from 'dotenv'; + +dotenv.config(); + +const userAgents = [ + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3', + 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0.3 Safari/605.1.15', + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:89.0) Gecko/20100101 Firefox/89.0' +]; + +export const quickFetch = async (req: Request, res: Response): Promise => { + const { url } = req.query; + + if (!url) { + res.status(400).json({ + status: 400, + error: { + code: 'MISSING_PARAM', + message: '缺少必要参数: url' + } + }); + return; + } + + try { + const response = await fetch(url as string, { + headers: { + 'User-Agent': userAgents[Math.floor(Math.random() * userAgents.length)], + Referer: 'https://www.google.com/', + 'Accept-Language': 'en-US,en;q=0.9', + Accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8', + Connection: 'keep-alive', + 'Cache-Control': 'no-cache' + } + }); + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + const data = await response.text(); + res.status(200).json({ + status: 200, + data: { + content: data + } + }); + } catch (error) { + console.error('Error fetching the page:', error); + res.status(500).json({ + status: 500, + error: { + code: 'INTERNAL_SERVER_ERROR', + message: '发生错误' + } + }); + } +}; + +export default { quickFetch }; diff --git a/plugins/webcrawler/SPIDER/src/controllers/readController.ts b/plugins/webcrawler/SPIDER/src/controllers/readController.ts new file mode 100644 index 000000000000..62b26e5b6512 --- /dev/null +++ b/plugins/webcrawler/SPIDER/src/controllers/readController.ts @@ -0,0 +1,148 @@ +import { Request, Response } from 'express'; +import puppeteer, { Page } from 'puppeteer'; +import * as cheerio from 'cheerio'; +import UserAgent from 'user-agents'; +import { setupPage } from '../utils/setupPage'; // 导入 setupPage 模块 +import dotenv from 'dotenv'; // 导入 dotenv 模块 +import { URL } from 'url'; // 导入 URL 模块 +import { handleSpecialWebsite } from '../specialHandlers'; // 导入 handleSpecialWebsite 模块 +import fetch from 'node-fetch'; +import { getCachedPage, updateCacheAsync } from '../utils/cacheUpdater'; // 导入缓存相关模块 + +dotenv.config(); // 加载环境变量 + +const detectWebsites = process.env.DETECT_WEBSITES?.split(',') || []; +const blacklistDomains = process.env.BLACKLIST ? JSON.parse(process.env.BLACKLIST) : []; + +export const readPage = async (req: Request, res: Response): Promise => { + const { queryUrl } = req.query; + console.log('-------'); + console.log(queryUrl); + console.log('-------'); + + if (!queryUrl) { + res.status(400).json({ + status: 400, + error: { + code: 'MISSING_PARAM', + message: '缺少必要参数: queryUrl' + } + }); + return; + } + + const urlDomain = new URL(queryUrl as string).hostname; + if (blacklistDomains.some((domain: string) => urlDomain.endsWith(domain))) { + res.status(403).json({ + status: 403, + error: { + code: 'BLACKLISTED_DOMAIN', + message: '该域名受到保护中' + } + }); + return; + } + + try { + const response = await fetch(queryUrl as string, { + headers: { + 'User-Agent': new UserAgent({ + deviceCategory: 'desktop', + platform: 'Linux x86_64' + }).toString(), + Referer: 'https://www.google.com/', + 'Accept-Language': 'en-US,en;q=0.9', + Accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8', + Connection: 'keep-alive', + 'Cache-Control': 'no-cache' + } + }); + + if (response.ok) { + const content = await response.text(); + const $ = cheerio.load(content); + const cleanedContent = $('body').html(); + + res.status(200).json({ + status: 200, + data: { + title: $('title').text(), + content: cleanedContent + } + }); + + await updateCacheAsync(queryUrl as string, cleanedContent || ''); + console.log('Page read successfully'); + return; + } else { + throw new Error(`HTTP error! status: ${response.status}`); + } + } catch (error) { + console.error('快速抓取页面时发生错误:', error); + } + + try { + const browser = await puppeteer.launch({ + ignoreDefaultArgs: ['--enable-automation'], + headless: true, + executablePath: '/usr/bin/chromium', // 明确指定 Chromium 路径 + pipe: true, + args: [ + '--no-sandbox', + '--disable-setuid-sandbox', + '--disable-dev-shm-usage', + '--disable-gpu' + // '--single-process' + ] + }); + const page = await browser.newPage(); + + // 检测是否需要特殊处理 + if ( + typeof queryUrl === 'string' && + detectWebsites.some((website) => queryUrl.includes(website)) + ) { + await setupPage(page); + } else { + const userAgent = new UserAgent({ deviceCategory: 'desktop', platform: 'Linux x86_64' }); + await page.setUserAgent(userAgent.toString()); + } + + const queryUrlSafe = new URL(queryUrl as string).toString(); + + await page.goto(queryUrlSafe, { waitUntil: 'load' }); + await page.waitForSelector('body'); + + const title = await page.title(); + let cleanedContent = await handleSpecialWebsite(page, queryUrl as string); + + if (!cleanedContent) { + const content = await page.content(); + const $ = cheerio.load(content); + cleanedContent = $('body').html(); + } + + await page.close(); + await browser.close(); + + res.status(200).json({ + status: 200, + data: { + title, + content: cleanedContent + } + }); + + await updateCacheAsync(queryUrl as string, cleanedContent || ''); + console.log('Page read successfully'); + } catch (error) { + console.error(error); + res.status(500).json({ + status: 500, + error: { + code: 'INTERNAL_SERVER_ERROR', + message: '读取页面时发生内部服务器错误' + } + }); + } +}; diff --git a/plugins/webcrawler/SPIDER/src/controllers/searchController.ts b/plugins/webcrawler/SPIDER/src/controllers/searchController.ts new file mode 100644 index 000000000000..26c84c67046a --- /dev/null +++ b/plugins/webcrawler/SPIDER/src/controllers/searchController.ts @@ -0,0 +1,132 @@ +import { Request, Response } from 'express'; +import { Cluster } from 'puppeteer-cluster'; +import dotenv from 'dotenv'; +import { performDeepSearch } from '../utils/deepSearch'; +import { fetchSearchResults as fetchBaiduResults } from '../engines/baiduEngine'; +import { fetchSearchResults as fetchSearchxngResults } from '../engines/searchxngEngine'; + +dotenv.config(); + +const strategies = JSON.parse(process.env.STRATEGIES || '[]'); +const detectWebsites = process.env.DETECT_WEBSITES?.split(',') || []; +const maxConcurrency = parseInt(process.env.MAX_CONCURRENCY || '10', 10); + +export const search = async (req: Request, res: Response): Promise => { + const { + query, + pageCount = 10, + needDetails = 'false', + engine = 'baidu', + categories = 'general' + } = req.query; + const needDetailsBool = needDetails === 'true'; + + if (!query) { + res.status(400).json({ + status: 400, + error: { + code: 'MISSING_PARAM', + message: '缺少必要参数: query' + } + }); + return; + } + let fetchSearchResults; + let searchUrlBase; + try { + if (engine === 'baidu') { + fetchSearchResults = fetchBaiduResults; + searchUrlBase = process.env.ENGINE_BAIDUURL; + } else if (engine === 'searchxng') { + fetchSearchResults = fetchSearchxngResults; + searchUrlBase = process.env.ENGINE_SEARCHXNGURL; + } else { + res.status(400).json({ + status: 400, + error: { + code: 'INVALID_ENGINE', + message: '无效的搜索引擎' + } + }); + return; + } + + const { resultUrls, results } = await fetchSearchResults( + query as string, + Number(pageCount), + searchUrlBase || '', + categories as string + ); + + //如果返回值为空,返回空数组 + if (results.size === 0) { + console.log('No results found'); + res.status(200).json({ + status: 200, + data: { + results: [] + } + }); + return; + } + + if (!needDetailsBool) { + console.log('Need details is false'); + results.forEach((value: any) => { + if (value.crawlStatus === 'Pending') { + value.crawlStatus = 'Success'; + } + }); + res.status(200).json({ + status: 200, + data: { + results: Array.from(results.values()) + } + }); + } else { + console.log('Need details is true'); + + const clusterInstance = await Cluster.launch({ + concurrency: Cluster.CONCURRENCY_CONTEXT, + maxConcurrency: maxConcurrency, + puppeteerOptions: { + ignoreDefaultArgs: ['--enable-automation'], + headless: 'true', + executablePath: '/usr/bin/chromium', // 明确指定 Chromium 路径 + pipe: true, + args: [ + '--no-sandbox', + '--disable-setuid-sandbox', + '--disable-dev-shm-usage', + '--disable-gpu' + ] + } + }); + + const sortedResults = await performDeepSearch( + clusterInstance, + resultUrls, + results, + strategies, + detectWebsites, + Number(pageCount) + ); + res.status(200).json({ + status: 200, + data: { + results: sortedResults.slice(0, Number(pageCount)) + } + }); + } + } catch (error) { + res.status(500).json({ + status: 500, + error: { + code: 'INTERNAL_SERVER_ERROR', + message: '发生错误' + } + }); + } +}; + +export default { search }; diff --git a/plugins/webcrawler/SPIDER/src/engines/baiduEngine.ts b/plugins/webcrawler/SPIDER/src/engines/baiduEngine.ts new file mode 100644 index 000000000000..ca75f0c1b527 --- /dev/null +++ b/plugins/webcrawler/SPIDER/src/engines/baiduEngine.ts @@ -0,0 +1,207 @@ +import { URL } from 'url'; +import { JSDOM } from 'jsdom'; +import puppeteer from 'puppeteer'; +import { setupPage } from '../utils/setupPage'; +import { Cluster } from 'puppeteer-cluster'; + +async function randomWait(min: number, max: number) { + // 随机等待时间 + const delay = Math.floor(Math.random() * (max - min + 1)) + min; + return new Promise((resolve) => setTimeout(resolve, delay)); +} + +export const fetchSearchResults = async ( + query: string, + pageCount: number, + searchUrlBase: string, + categories: string +) => { + console.log(`Fetching Baidu search results for query: ${query}`); + // 如果 searchUrlBase 为空,返回空数组 + if (!searchUrlBase) { + return { resultUrls: [], results: new Map() }; + } + const resultUrls: string[] = []; + const results = new Map(); + + const pagesToFetch = Math.ceil(pageCount / 10); + + const browser = await puppeteer.launch({ + ignoreDefaultArgs: ['--enable-automation'], + headless: true, + executablePath: '/usr/bin/chromium', // 明确指定 Chromium 路径 + pipe: true, + args: [ + '--no-sandbox', + '--disable-setuid-sandbox', + '--disable-dev-shm-usage', + '--disable-gpu' + // '--single-process' + ] + }); + + const page = await browser.newPage(); + await setupPage(page); + + for (let i = 0; i < pagesToFetch; i++) { + const searchUrl = new URL(`${searchUrlBase}?wd=${encodeURIComponent(query)}&pn=${i * 10}`); + console.log(`Fetching page ${i + 1} from Baidu: ${searchUrl.toString()}`); + let retryCount = 0; + let success = false; + + while (retryCount < 5 && !success) { + try { + console.time(`Page Load Time for page ${i + 1}`); + await page.goto(searchUrl.toString(), { waitUntil: 'load' }); + console.timeEnd(`Page Load Time for page ${i + 1}`); + + let content = await page.content(); + let dom = new JSDOM(content); + let document = dom.window.document; + console.log(document.title); + + // 如果是百度安全验证页面,重新设置页面并重新访问 + if (document.title.includes('百度安全验证')) { + console.log('Detected Baidu security verification, retrying...'); + await setupPage(page); + retryCount++; + //随机等待时间 + await randomWait(1000, 3000); + continue; + } + + // 解析搜索结果 + console.time(`Link Retrieval Time for page ${i + 1}`); + + const resultContainers = document.querySelectorAll('.result.c-container'); + for (const result of resultContainers) { + if (resultUrls.length > pageCount + 5) { + break; + } + const titleElement = result.querySelector('h3 a'); + const title = titleElement ? titleElement.textContent : ''; + const url = titleElement ? titleElement.getAttribute('href') : ''; + const contentElement = result.querySelector('[class^="content"]'); + const content = contentElement ? contentElement.textContent : ''; + + if (url) { + resultUrls.push(url); + results.set(url, { + title, + url, + snippet: content, + source: 'baidu', + crawlStatus: 'Pending', + score: 0 + }); + } + } + console.timeEnd(`Link Retrieval Time for page ${i + 1}`); + success = true; + } catch (error) { + console.error(`Error fetching page ${i + 1}:`, error); + retryCount++; + } + } + } + + await browser.close(); + + console.log('fetch all fake urls'); + + // 快速检索真实 URL + const urlsToProcessWithPuppeteer = []; + for (const url of resultUrls) { + try { + const response = await fetch(url, { + headers: { + 'User-Agent': + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3', + Referer: 'https://www.google.com/', + 'Accept-Language': 'en-US,en;q=0.9', + Accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8', + Connection: 'keep-alive', + 'Cache-Control': 'no-cache' + } + }); + + if (response.ok) { + const realUrl = response.url; + console.log('realurl:', realUrl); + const result = results.get(url); + if (result) { + result.url = realUrl; + result.crawlStatus = 'Success'; + } + } else { + throw new Error(`HTTP error! status: ${response.status}`); + } + } catch (error) { + console.error(`Error fetching original URL for ${url}:`, error); + urlsToProcessWithPuppeteer.push(url); + } + } + + console.log('pass quickfetch'); + + // 并发处理真实 URL + const cluster = await Cluster.launch({ + concurrency: Cluster.CONCURRENCY_CONTEXT, + maxConcurrency: 10, + puppeteerOptions: { + ignoreDefaultArgs: ['--enable-automation'], + headless: 'true', + executablePath: '/usr/bin/chromium', // 明确指定 Chromium 路径 + pipe: true, + args: ['--no-sandbox', '--disable-setuid-sandbox', '--disable-dev-shm-usage', '--disable-gpu'] + } + }); + + let failedUrlCount = 0; + + await cluster.task(async ({ page, data: url }) => { + let retryUrlCount = 0; + let urlSuccess = false; + while (retryUrlCount < 3 && !urlSuccess) { + console.log(`Fetching original URL for ${url}, attempt ${retryUrlCount + 1}`); + try { + await page.goto(url, { waitUntil: 'load' }); + // 检查页面是否被分离 + if (page.isClosed()) { + throw new Error('Page has been closed'); + } + const realUrl = page.url(); // 获取真实 URL + const result = results.get(url); + if (result) { + result.url = realUrl; + result.crawlStatus = 'Success'; + } + urlSuccess = true; + } catch (error) { + console.error(`Error fetching original URL, retrying...`, error); + retryUrlCount++; + await randomWait(1000, 3000); + } + } + if (!urlSuccess) { + failedUrlCount++; + } + }); + + for (const url of urlsToProcessWithPuppeteer) { + cluster.queue(url); + } + + await cluster.idle(); + await cluster.close(); + + console.log(`Number of URLs that failed to return a real URL: ${failedUrlCount}`); + + // 过滤并返回前 pageCount 个结果 + const filteredResults = Array.from(results.values()).slice(0, pageCount); + + return { + resultUrls: filteredResults.map((result) => result.url), + results: new Map(filteredResults.map((result) => [result.url, result])) + }; +}; diff --git a/plugins/webcrawler/SPIDER/src/engines/searchxngEngine.ts b/plugins/webcrawler/SPIDER/src/engines/searchxngEngine.ts new file mode 100644 index 000000000000..305fb8114de0 --- /dev/null +++ b/plugins/webcrawler/SPIDER/src/engines/searchxngEngine.ts @@ -0,0 +1,64 @@ +import axios from 'axios'; +import { URL } from 'url'; +import dotenv from 'dotenv'; + +dotenv.config(); + +const blacklistDomains = process.env.BLACKLIST ? JSON.parse(process.env.BLACKLIST) : []; + +export const fetchSearchResults = async ( + query: string, + pageCount: number, + searchUrlBase: string, + categories: string +) => { + const MAX_PAGES = (pageCount / 10 + 1) * 2 + 1; // 最多搜索的页面数 + //如果searchUrlBase为空,返回空数组,pagecount是需要搜索结果的数量 + if (!searchUrlBase) { + return { resultUrls: [], results: new Map() }; + } + const resultUrls: string[] = []; + const results = new Map(); + + let fetchedResultsCount = 0; + let pageIndex = 0; + + while (fetchedResultsCount < pageCount && pageIndex < MAX_PAGES) { + const searchUrl = new URL( + `${searchUrlBase}?q=${encodeURIComponent(query)}&pageno=${pageIndex + 1}&format=json&categories=${categories}` + ); + console.log(`Fetching page ${pageIndex + 1} from SearchXNG: ${searchUrl.toString()}`); + const response = await axios.get(searchUrl.toString()); + const jsonResults = response.data.results; + + for (let index = 0; index < jsonResults.length; index++) { + const result = jsonResults[index]; + const resultDomain = new URL(result.url).hostname; + if ( + blacklistDomains.some((domain: string) => resultDomain.endsWith(domain)) || + resultDomain.includes('zhihu') + ) { + continue; + } + resultUrls.push(result.url); + results.set(result.url, { + title: result.title, + url: result.url, + snippet: result.content, + source: result.engine, + crawlStatus: 'Pending', + score: result.score + }); + fetchedResultsCount++; + if (fetchedResultsCount >= pageCount) { + break; + } + } + pageIndex++; + if (jsonResults.length === 0) { + break; // 如果没有更多结果,退出循环 + } + } + + return { resultUrls, results }; +}; diff --git a/plugins/webcrawler/SPIDER/src/index.ts b/plugins/webcrawler/SPIDER/src/index.ts new file mode 100644 index 000000000000..bea94a7a6d9d --- /dev/null +++ b/plugins/webcrawler/SPIDER/src/index.ts @@ -0,0 +1,18 @@ +import express, { Application } from 'express'; +import bodyParser from 'body-parser'; +import searchRoutes from './routes/searchRoutes'; +import readRoutes from './routes/readRoutes'; +import quickfetchRoutes from './routes/quickfetchRoutes'; +import dotenv from 'dotenv'; + +dotenv.config(); + +const app: Application = express(); + +app.use(bodyParser.json()); +app.use('/api', searchRoutes); +app.use('/api', readRoutes); +app.use('/api', quickfetchRoutes); + +const PORT = process.env.PORT || 3000; +app.listen(PORT, () => console.log(`Server running on port ${PORT}`)); diff --git a/plugins/webcrawler/SPIDER/src/middleware/authMiddleware.ts b/plugins/webcrawler/SPIDER/src/middleware/authMiddleware.ts new file mode 100644 index 000000000000..76ca9ff4af41 --- /dev/null +++ b/plugins/webcrawler/SPIDER/src/middleware/authMiddleware.ts @@ -0,0 +1,21 @@ +import { Request, Response, NextFunction } from 'express'; + +const authMiddleware = (req: Request, res: Response, next: NextFunction) => { + const bearerHeader = req.headers['authorization']; + + if (bearerHeader) { + console.log('bearerHeader:' + bearerHeader); + const bearer = bearerHeader.split(' '); + const bearerToken = bearer[1]; + + if (bearerToken === process.env.ACCESS_TOKEN) { + next(); + } else { + res.status(403).json({ message: 'Invalid token' }); + } + } else { + res.status(401).json({ message: 'Bearer token not found' }); + } +}; + +export default authMiddleware; diff --git a/plugins/webcrawler/SPIDER/src/routes/quickfetchRoutes.ts b/plugins/webcrawler/SPIDER/src/routes/quickfetchRoutes.ts new file mode 100644 index 000000000000..139c9b24e735 --- /dev/null +++ b/plugins/webcrawler/SPIDER/src/routes/quickfetchRoutes.ts @@ -0,0 +1,9 @@ +import express from 'express'; +import { quickFetch } from '../controllers/quickfetchController'; +import authMiddleware from '../middleware/authMiddleware'; + +const readRoutes = express.Router(); + +readRoutes.get('/quickFetch', authMiddleware, quickFetch); + +export default readRoutes; diff --git a/plugins/webcrawler/SPIDER/src/routes/readRoutes.ts b/plugins/webcrawler/SPIDER/src/routes/readRoutes.ts new file mode 100644 index 000000000000..c50447d6a27b --- /dev/null +++ b/plugins/webcrawler/SPIDER/src/routes/readRoutes.ts @@ -0,0 +1,9 @@ +import express from 'express'; +import { readPage } from '../controllers/readController'; +import authMiddleware from '../middleware/authMiddleware'; + +const readRoutes = express.Router(); + +readRoutes.get('/read', authMiddleware, readPage); + +export default readRoutes; diff --git a/plugins/webcrawler/SPIDER/src/routes/searchRoutes.ts b/plugins/webcrawler/SPIDER/src/routes/searchRoutes.ts new file mode 100644 index 000000000000..b3b92035f1c8 --- /dev/null +++ b/plugins/webcrawler/SPIDER/src/routes/searchRoutes.ts @@ -0,0 +1,9 @@ +import express from 'express'; +import searchController from '../controllers/searchController'; +import authMiddleware from '../middleware/authMiddleware'; + +const searchRoutes = express.Router(); + +searchRoutes.get('/search', authMiddleware, searchController.search); + +export default searchRoutes; diff --git a/plugins/webcrawler/SPIDER/src/specialHandlers/index.ts b/plugins/webcrawler/SPIDER/src/specialHandlers/index.ts new file mode 100644 index 000000000000..9b8f4000b58b --- /dev/null +++ b/plugins/webcrawler/SPIDER/src/specialHandlers/index.ts @@ -0,0 +1,26 @@ +import { Page } from 'puppeteer'; + +export const handleSpecialWebsite = async (page: Page, url: string): Promise => { + if (url.includes('blog.csdn.net')) { + await page.waitForSelector('article'); + const content = await page.$eval('article', (el) => el.innerHTML); + return content; + } + if (url.includes('zhuanlan.zhihu.com')) { + console.log('是知乎,需要点击按掉!'); + console.log(await page.content()); + if ( + (await page.content()).includes( + '{"error":{"message":"您当前请求存在异常,暂时限制本次访问。如有疑问,您可以通过手机摇一摇或登录后私信知乎小管家反馈。","code":40362}}' + ) + ) + return null; + await page.waitForSelector('button[aria-label="关闭"]'); + await page.click('button[aria-label="关闭"]'); // 使用 aria-label 选择按钮 + await page.waitForSelector('article'); + const content = await page.$eval('article', (el) => el.innerHTML); + return content; + } + // 可以添加更多特殊网站的处理逻辑 + return null; +}; diff --git a/plugins/webcrawler/SPIDER/src/utils/cacheUpdater.ts b/plugins/webcrawler/SPIDER/src/utils/cacheUpdater.ts new file mode 100644 index 000000000000..3ceb6c2191ba --- /dev/null +++ b/plugins/webcrawler/SPIDER/src/utils/cacheUpdater.ts @@ -0,0 +1,77 @@ +import NodeCache from 'node-cache'; +import { MongoClient } from 'mongodb'; +import crypto from 'crypto'; +import dotenv from 'dotenv'; + +dotenv.config(); + +const cache = new NodeCache({ stdTTL: parseInt(process.env.STD_TTL || '3600') }); +const mongoClient = new MongoClient(process.env.MONGODB_URI || 'mongodb://localhost:27017'); +const dbName = 'pageCache'; +const collectionName = 'pages'; + +const connectToMongo = async () => { + await mongoClient.connect(); + return mongoClient.db(dbName); +}; + +const createTTLIndex = async () => { + try { + const db = await connectToMongo(); + await db + .collection(collectionName) + .createIndex( + { updatedAt: 1 }, + { expireAfterSeconds: parseInt(process.env.EXPIRE_AFTER_SECONDS || '9000') } + ); + console.log('TTL index created successfully'); + } catch (error) { + console.error('Error creating TTL index:', error); + } +}; + +const getPageHash = (content: string) => { + return crypto.createHash('md5').update(content).digest('hex'); +}; + +export const getCachedPage = async (url: string) => { + const cachedPage = cache.get(url); + if (cachedPage) return cachedPage; + + try { + const db = await connectToMongo(); + const page = await db.collection(collectionName).findOne({ url }); + if (page) cache.set(url, page); + return page; + } catch (error) { + console.error('Error getting cached page:', error); + throw error; + } +}; + +const savePageToCache = async (url: string, content: string) => { + const hash = getPageHash(content); + const page = { url, content, hash, updatedAt: new Date() }; + + cache.set(url, page); // 更新内存缓存 + + try { + const db = await connectToMongo(); + await db.collection(collectionName).updateOne({ url }, { $set: page }, { upsert: true }); // 更新持久化缓存 + } catch (error) { + console.error('Error saving page to cache:', error); + throw error; + } +}; + +export const updateCacheAsync = async (url: string, content: string) => { + await savePageToCache(url, content); +}; + +process.on('SIGINT', async () => { + await mongoClient.close(); + process.exit(0); +}); + +// 在应用启动时创建 TTL 索引 +createTTLIndex(); diff --git a/plugins/webcrawler/SPIDER/src/utils/deepSearch.ts b/plugins/webcrawler/SPIDER/src/utils/deepSearch.ts new file mode 100644 index 000000000000..1053c91a568e --- /dev/null +++ b/plugins/webcrawler/SPIDER/src/utils/deepSearch.ts @@ -0,0 +1,158 @@ +import { Cluster } from 'puppeteer-cluster'; +import * as cheerio from 'cheerio'; +import UserAgent from 'user-agents'; +import { setupPage } from './setupPage'; +import { getCachedPage, updateCacheAsync } from './cacheUpdater'; +import { handleSpecialWebsite } from '../specialHandlers'; +import fetch from 'node-fetch'; + +interface CachedPage { + url: string; + content: string; + hash: string; + updatedAt: Date; +} + +export const performDeepSearch = async ( + clusterInstance: Cluster, + resultUrls: string[], + results: Map, + strategies: any[], + detectWebsites: string[], + pageCount: number +) => { + const tasks = []; + + await clusterInstance.task(async ({ page, data: { searchUrl } }) => { + try { + const cachedPage = (await getCachedPage(searchUrl)) as CachedPage | null; + if (cachedPage) { + const result = results.get(searchUrl); + if (result) { + result.content = cachedPage.content; + result.crawlStatus = 'Success'; + } + return; + } + } catch (error) { + console.error(`从缓存获取页面 ${searchUrl} 时发生错误:`, error); + results.set(searchUrl, { + url: searchUrl, + error: (error as Error).message, + crawlStatus: 'Failed' + }); + return; + } + + try { + const response = await fetch(searchUrl, { + headers: { + 'User-Agent': new UserAgent({ + deviceCategory: 'desktop', + platform: 'Linux x86_64' + }).toString(), + Referer: 'https://www.google.com/', + 'Accept-Language': 'en-US,en;q=0.9', + Accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8', + Connection: 'keep-alive', + 'Cache-Control': 'no-cache' + } + }); + + if (response.ok) { + const content = await response.text(); + const $ = cheerio.load(content); + const cleanedContent = $('body').html() || ''; + + const result = results.get(searchUrl); + if (result) { + result.content = cleanedContent; + result.crawlStatus = 'Success'; + } + + await updateCacheAsync(searchUrl, cleanedContent || ''); + return; + } else { + throw new Error(`HTTP error! status: ${response.status}`); + } + } catch (error) { + console.error(`快速抓取页面 ${searchUrl} 时发生错误:`, error); + } + + try { + if (detectWebsites.some((website) => searchUrl.includes(website))) { + await setupPage(page); + } else { + const userAgent = new UserAgent({ deviceCategory: 'desktop', platform: 'Linux x86_64' }); + await page.setUserAgent(userAgent.toString()); + } + } catch (error) { + console.error(`访问页面 ${searchUrl} 设置用户代理时发生错误:`, error); + } + + let pageLoaded = false; + let pageLoadError: Error | null = null; + for (const strategy of strategies) { + try { + await page.goto(searchUrl, { waitUntil: strategy.waitUntil, timeout: strategy.timeout }); + pageLoaded = true; + break; + } catch (error: any) { + if (error.name === 'TimeoutError') { + pageLoadError = error; + continue; + } else { + pageLoadError = error; + throw error; + } + } + } + if (!pageLoaded) { + const result = results.get(searchUrl); + if (result) { + result.error = pageLoadError; + result.crawlStatus = 'Failed'; + } + return; + } + + try { + let cleanedContent = await handleSpecialWebsite(page, searchUrl); + if (!cleanedContent) { + const content = await page.content(); + const $ = cheerio.load(content); + cleanedContent = $('body').html() || ''; + } + + const result = results.get(searchUrl); + if (result) { + result.content = cleanedContent; + result.crawlStatus = 'Success'; + } + + await updateCacheAsync(searchUrl, cleanedContent || ''); + } catch (error) { + results.set(searchUrl, { + url: searchUrl, + error: (error as Error).message, + crawlStatus: 'Failed' + }); + } finally { + await page.close().catch(() => {}); + } + }); + + for (const url of resultUrls) { + if (tasks.length >= pageCount + 10) { + break; + } + tasks.push(clusterInstance.queue({ searchUrl: url })); + } + + await Promise.all(tasks); + + await clusterInstance.idle(); + await clusterInstance.close(); + + return Array.from(results.values()).sort((a, b) => b.score - a.score); +}; diff --git a/plugins/webcrawler/SPIDER/src/utils/setupPage.ts b/plugins/webcrawler/SPIDER/src/utils/setupPage.ts new file mode 100644 index 000000000000..cae1f8878781 --- /dev/null +++ b/plugins/webcrawler/SPIDER/src/utils/setupPage.ts @@ -0,0 +1,81 @@ +import { Page } from 'puppeteer'; +import randomUseragent from 'random-useragent'; +import dotenv from 'dotenv'; + +dotenv.config(); +const getRandomUserAgent = () => { + return randomUseragent.getRandom(); +}; + +const getRandomPlatform = () => { + const platforms = ['Win32', 'MacIntel', 'Linux x86_64']; + return platforms[Math.floor(Math.random() * platforms.length)]; +}; + +//代理池 +const validateproxy = process.env.VALIDATE_PROXY ? JSON.parse(process.env.VALIDATE_PROXY) : []; + +const getRandomProxy = () => { + return validateproxy.length > 0 + ? validateproxy[Math.floor(Math.random() * validateproxy.length)] + : null; +}; + +const getRandomLanguages = () => { + const languages = [ + ['zh-CN', 'zh', 'en'], + ['en-US', 'en', 'fr'], + ['es-ES', 'es', 'en'] + ]; + return languages[Math.floor(Math.random() * languages.length)]; +}; + +export const setupPage = async (page: Page): Promise => { + const proxy = getRandomProxy(); + if (proxy) { + await page.authenticate({ + username: proxy.ip, + password: proxy.port.toString() + }); + } + + await page.evaluateOnNewDocument(() => { + const newProto = (navigator as any).__proto__; + delete newProto.webdriver; + (navigator as any).__proto__ = newProto; + (window as any).chrome = {}; + (window as any).chrome.app = { + InstallState: 'testt', + RunningState: 'estt', + getDetails: 'stte', + getIsInstalled: 'ttes' + }; + (window as any).chrome.csi = function () {}; + (window as any).chrome.loadTimes = function () {}; + (window as any).chrome.runtime = function () {}; + Object.defineProperty(navigator, 'userAgent', { + get: () => getRandomUserAgent() + }); + Object.defineProperty(navigator, 'platform', { + get: () => getRandomPlatform() + }); + Object.defineProperty(navigator, 'plugins', { + get: () => [ + { + description: 'Shockwave Flash', + filename: 'pepflashplayer.dll', + length: 1, + name: 'Shockwave Flash' + } + ] + }); + Object.defineProperty(navigator, 'languages', { + get: () => getRandomLanguages() + }); + const originalQuery = (window.navigator.permissions as any).query; + (window.navigator.permissions as any).query = (parameters: any) => + parameters.name === 'notifications' + ? Promise.resolve({ state: Notification.permission } as PermissionStatus) + : originalQuery(parameters); + }); +}; diff --git a/plugins/webcrawler/SPIDER/tsconfig.json b/plugins/webcrawler/SPIDER/tsconfig.json new file mode 100644 index 000000000000..2b443cc2a361 --- /dev/null +++ b/plugins/webcrawler/SPIDER/tsconfig.json @@ -0,0 +1,113 @@ +{ + "compilerOptions": { + /* Visit https://aka.ms/tsconfig to read more about this file */ + /* Projects */ + // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ + // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ + // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ + // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ + // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ + // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ + "types": ["node"], + /* Language and Environment */ + "target": "es6", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ + // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ + // "jsx": "preserve", /* Specify what JSX code is generated. */ + // "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */ + // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ + // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ + // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ + // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ + // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ + // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ + // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ + // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ + + /* Modules */ + //"module": "es6", /* Specify what module code is generated. */ + "rootDir": "./src", /* Specify the root folder within your source files. */ + "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */ + // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ + // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ + // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ /* Specify type package names to be included without being referenced in a source file. */ + // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ + // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ + // "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */ + // "rewriteRelativeImportExtensions": true, /* Rewrite '.ts', '.tsx', '.mts', and '.cts' file extensions in relative import paths to their JavaScript equivalent in output files. */ + // "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */ + // "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */ + // "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */ + // "noUncheckedSideEffectImports": true, /* Check side effect imports. */ + // "resolveJsonModule": true, /* Enable importing .json files. */ + // "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */ + // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ + + /* JavaScript Support */ + // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */ + // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ + // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ + + /* Emit */ + // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ + // "declarationMap": true, /* Create sourcemaps for d.ts files. */ + // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ + // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ + // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ + // "noEmit": true, /* Disable emitting files from a compilation. */ + // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ + "outDir": "./dist", /* Specify an output folder for all emitted files. */ + // "removeComments": true, /* Disable emitting comments. */ + // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ + // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ + // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ + // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ + // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ + // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ + // "newLine": "crlf", /* Set the newline character for emitting files. */ + // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ + // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ + // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ + // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ + // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ + + /* Interop Constraints */ + // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ + // "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */ + // "isolatedDeclarations": true, /* Require sufficient annotation on exports so other tools can trivially generate declaration files. */ + // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ + "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */ + // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ + "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ + + /* Type Checking */ + "typeRoots": ["./node_modules/@types"], + "strict": true, /* Enable all strict type-checking options. */ + // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ + // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ + // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ + // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ + // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ + // "strictBuiltinIteratorReturn": true, /* Built-in iterators are instantiated with a 'TReturn' type of 'undefined' instead of 'any'. */ + // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ + // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ + // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ + // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ + // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ + // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ + // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ + // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ + // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ + // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ + // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ + // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ + // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ + + /* Completeness */ + // "skipDefaultLibCheck": true, + // /* Skip type checking .d.ts files that are included with TypeScript. */ + "skipLibCheck": true/* Skip type checking all .d.ts files. */ + + }, + "include": ["src/**/*.ts"], + "exclude": ["node_modules"] +} diff --git a/plugins/webcrawler/SPIDER/webpack.config.js b/plugins/webcrawler/SPIDER/webpack.config.js new file mode 100644 index 000000000000..965f1f500501 --- /dev/null +++ b/plugins/webcrawler/SPIDER/webpack.config.js @@ -0,0 +1,55 @@ +// 引入path包 +const path = require('path') +require('dotenv').config(); +const mode = process.env.NODE_ENV || 'development' + +const nodeExternals = require('webpack-node-externals'); +module.exports = { + target: 'node', // 指定构建目标为 Node.js + externals: [nodeExternals()], // 排除 node_modules + // 指定入口文件 + entry: "./src/index.ts", + + // 指定打包文件所在目录 + output: { + path: path.resolve(__dirname, 'dist'), + // 打包后文件的名称 + filename: "bundle.js" + }, + resolve: { + extensions: ['.ts', '.tsx', '.js', '.json'], + fallback: { + "zlib": require.resolve("browserify-zlib"), + "querystring": require.resolve("querystring-es3"), + "path": require.resolve("path-browserify"), + "crypto": require.resolve("crypto-browserify"), + "stream": require.resolve("stream-browserify"), + "os": require.resolve("os-browserify/browser"), + "http": require.resolve("stream-http"), + "net": false, + "string_decoder": require.resolve("string_decoder/"), + "url": require.resolve("url/"), + "buffer": require.resolve("buffer/"), + "util": require.resolve("util/"), + // 新增 assert 的 fallback + "assert": require.resolve("assert/"), + // 处理新出现的 vm 警告 + "vm": require.resolve("vm-browserify"), + "fs": false + } + }, + + // 指定webpack打包的时候要使用的模块 + module: { + // 指定要价在的规则 + rules: [ + { + // test指定的是规则生效的文件,意思是,用ts-loader来处理以ts为结尾的文件 + test: /\.ts$/, + use: 'ts-loader', + exclude: /node_modules/ + } + ] + }, + mode, +} diff --git a/plugins/webcrawler/docker-compose.yaml b/plugins/webcrawler/docker-compose.yaml new file mode 100644 index 000000000000..76649f826544 --- /dev/null +++ b/plugins/webcrawler/docker-compose.yaml @@ -0,0 +1,124 @@ +name: spider +version: "0.0.1" + +services: + caddy: + container_name: caddy + image: docker.io/library/caddy:2-alpine + network_mode: host + restart: unless-stopped + volumes: + - ./Caddyfile:/etc/caddy/Caddyfile:ro + - caddy-data:/data:rw + - caddy-config:/config:rw + environment: + - SEARXNG_HOSTNAME=${SEARXNG_HOSTNAME:-http://localhost} + - SEARXNG_TLS=${LETSENCRYPT_EMAIL:-internal} + cap_add: + - NET_BIND_SERVICE + cap_drop: + - ALL + logging: + driver: "json-file" + options: + max-size: "1m" + max-file: "1" + + redis: + container_name: redis + image: docker.io/valkey/valkey:8-alpine + command: valkey-server --save 30 1 --loglevel warning + restart: unless-stopped + networks: + - searxng + volumes: + - valkey-data2:/data + cap_drop: + - ALL + cap_add: + - SETGID + - SETUID + - DAC_OVERRIDE + logging: + driver: "json-file" + options: + max-size: "1m" + max-file: "1" + + searxng: + container_name: searxng + image: docker.io/searxng/searxng:latest + restart: unless-stopped + networks: + - searxng + ports: + - "127.0.0.1:8080:8080" + volumes: + - ./searxng:/etc/searxng:rw + environment: + - SEARXNG_BASE_URL=https://${SEARXNG_HOSTNAME:-localhost}/ + - UWSGI_WORKERS=${SEARXNG_UWSGI_WORKERS:-4} + - UWSGI_THREADS=${SEARXNG_UWSGI_THREADS:-4} + env_file: + - .searchxng.env + cap_drop: + - ALL + cap_add: + - CHOWN + - SETGID + - SETUID + logging: + driver: "json-file" + options: + max-size: "1m" + max-file: "1" + + mongodb: + container_name: mongodb + image: mongo:4.4 + restart: unless-stopped + networks: + - searxng + ports: + - "27017:27017" + volumes: + - mongo-data:/data/db + environment: + MONGO_INITDB_ROOT_USERNAME: root + MONGO_INITDB_ROOT_PASSWORD: example + logging: + driver: "json-file" + options: + max-size: "1m" + max-file: "1" + + nodeapp: + container_name: main + build: + context: . + ports: + - "3000:3000" + networks: + - searxng + depends_on: + - mongodb + logging: + driver: "json-file" + options: + max-size: "1m" + max-file: "1" + volumes: + - /dev/shm:/dev/shm + deploy: + resources: + limits: + memory: 4G + cpus: '2.0' +networks: + searxng: + +volumes: + caddy-data: + caddy-config: + valkey-data2: + mongo-data: \ No newline at end of file diff --git a/plugins/webcrawler/searxng-docker.service.template b/plugins/webcrawler/searxng-docker.service.template new file mode 100644 index 000000000000..29a508c0aa8d --- /dev/null +++ b/plugins/webcrawler/searxng-docker.service.template @@ -0,0 +1,16 @@ +[Unit] +Description=SearXNG service +Requires=docker.service +After=docker.service + +[Service] +Restart=on-failure + +Environment=SEARXNG_DOCKERCOMPOSEFILE=docker-compose.yaml + +WorkingDirectory=/usr/local/searxng-docker +ExecStart=/usr/local/bin/docker compose -f ${SEARXNG_DOCKERCOMPOSEFILE} up --remove-orphans +ExecStop=/usr/local/bin/docker compose -f ${SEARXNG_DOCKERCOMPOSEFILE} down + +[Install] +WantedBy=multi-user.target diff --git a/plugins/webcrawler/searxng/limiter.toml b/plugins/webcrawler/searxng/limiter.toml new file mode 100644 index 000000000000..190f8df58225 --- /dev/null +++ b/plugins/webcrawler/searxng/limiter.toml @@ -0,0 +1,6 @@ +# This configuration file updates the default configuration file +# See https://github.com/searxng/searxng/blob/master/searx/limiter.toml + +[botdetection.ip_limit] +# activate link_token method in the ip_limit method +link_token = true diff --git a/plugins/webcrawler/searxng/settings.yml b/plugins/webcrawler/searxng/settings.yml new file mode 100644 index 000000000000..717966598699 --- /dev/null +++ b/plugins/webcrawler/searxng/settings.yml @@ -0,0 +1,38 @@ +# see https://docs.searxng.org/admin/settings/settings.html#settings-use-default-settings +use_default_settings: true +server: + # base_url is defined in the SEARXNG_BASE_URL environment variable, see .env and docker-compose.yml + secret_key: "01042f00ae8bb522a9c03d3e7e1910318208a2c9fbdd23a6315577a9c98553a8" # change this! + limiter: false # can be disabled for a private instance + image_proxy: true +ui: + static_use_hash: true + # 启用 cn 分类 + enabled_categories: [cn, general, images] # 按需添加其他分类 + # 或者定义分类显示顺序 + categories_order: [cn, general, images] +redis: + url: redis://redis:6379/0 +engines: + - name: bing + disabled: false + categories: cn + #- name: bilibili + # engine: bilibili + # shortcut: bil + # disabled: false + # categories: cn + - name : baidu + engine : json_engine + paging : True + first_page_num : 0 + search_url : https://www.baidu.com/s?tn=json&wd={query}&pn={pageno}&rn=50 + url_query : url + title_query : title + content_query : abs + categories : cn + +search: + formats: + - html + - json diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b8c5015ce2a8..e2492a00e606 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -11,6 +11,9 @@ importers: '@chakra-ui/cli': specifier: ^2.4.1 version: 2.4.1 + '@vitest/coverage-v8': + specifier: 3.0.5 + version: 3.0.5(vitest@3.0.5(@types/debug@4.1.12)(@types/node@22.7.8)(jsdom@26.0.0)(sass@1.77.8)(terser@5.31.3)) husky: specifier: ^8.0.3 version: 8.0.3 @@ -22,16 +25,22 @@ importers: version: 13.3.0 next-i18next: specifier: 15.3.0 - version: 15.3.0(i18next@23.11.5)(next@14.2.5(@babel/core@7.24.9)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.8))(react-i18next@14.1.2(i18next@23.11.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1) + version: 15.3.0(i18next@23.11.5)(next@14.2.21(@babel/core@7.24.9)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.8))(react-i18next@14.1.2(i18next@23.11.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1) prettier: specifier: 3.2.4 version: 3.2.4 react-i18next: specifier: 14.1.2 version: 14.1.2(i18next@23.11.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + vitest: + specifier: ^3.0.2 + version: 3.0.5(@types/debug@4.1.12)(@types/node@22.7.8)(jsdom@26.0.0)(sass@1.77.8)(terser@5.31.3) + vitest-mongodb: + specifier: ^1.0.1 + version: 1.0.1(socks@2.8.3) zhlint: specifier: ^0.7.4 - version: 0.7.4(@types/node@22.7.8)(sass@1.77.8)(terser@5.31.3)(typescript@5.5.3) + version: 0.7.4(@types/node@22.7.8)(jsdom@26.0.0)(sass@1.77.8)(terser@5.31.3)(typescript@5.5.3) packages/global: dependencies: @@ -42,8 +51,8 @@ importers: specifier: ^1.2.8 version: 1.2.8 axios: - specifier: ^1.5.1 - version: 1.7.2 + specifier: ^1.8.2 + version: 1.8.2 cron-parser: specifier: ^4.9.0 version: 4.9.0 @@ -66,8 +75,8 @@ importers: specifier: ^4.0.1 version: 4.0.2 next: - specifier: 14.2.5 - version: 14.2.5(@babel/core@7.24.9)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.8) + specifier: 14.2.21 + version: 14.2.21(@babel/core@7.24.9)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.8) openai: specifier: 4.61.0 version: 4.61.0(encoding@0.1.13) @@ -91,8 +100,8 @@ importers: specifier: ^8.6.6 version: 8.11.10 axios: - specifier: ^1.5.1 - version: 1.7.2 + specifier: ^1.8.2 + version: 1.8.2 cheerio: specifier: 1.0.0-rc.12 version: 1.0.0-rc.12 @@ -152,8 +161,8 @@ importers: specifier: 2.4.2 version: 2.4.2 axios: - specifier: ^1.5.1 - version: 1.7.7 + specifier: ^1.8.2 + version: 1.8.2 chalk: specifier: ^5.3.0 version: 5.3.0 @@ -194,8 +203,8 @@ importers: specifier: ^2.2.3 version: 2.2.3 jsonpath-plus: - specifier: ^10.1.0 - version: 10.1.0 + specifier: ^10.3.0 + version: 10.3.0 jsonwebtoken: specifier: ^9.0.2 version: 9.0.2 @@ -206,17 +215,17 @@ importers: specifier: ^1.6.0 version: 1.8.0 mongoose: - specifier: ^7.0.2 - version: 7.8.2 + specifier: ^8.10.1 + version: 8.10.2(socks@2.8.3) multer: specifier: 1.4.5-lts.1 version: 1.4.5-lts.1 next: - specifier: 14.2.5 - version: 14.2.5(@babel/core@7.24.9)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.8) + specifier: 14.2.21 + version: 14.2.21(@babel/core@7.24.9)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.8) nextjs-cors: specifier: ^2.2.0 - version: 2.2.0(next@14.2.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.8)) + version: 2.2.0(next@14.2.21(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.8)) node-cron: specifier: ^3.0.3 version: 3.0.3 @@ -288,6 +297,27 @@ importers: specifier: workspace:* version: link:../service + packages/test: + devDependencies: + '@testing-library/dom': + specifier: ^10.4.0 + version: 10.4.0 + '@testing-library/react': + specifier: ^16.2.0 + version: 16.2.0(@testing-library/dom@10.4.0)(@types/react-dom@18.3.0)(@types/react@18.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@vitejs/plugin-react': + specifier: ^4.3.4 + version: 4.3.4(vite@5.3.4(@types/node@22.7.8)(sass@1.77.8)(terser@5.31.3)) + jsdom: + specifier: ^26.0.0 + version: 26.0.0 + vite-tsconfig-paths: + specifier: ^5.1.4 + version: 5.1.4(typescript@5.5.3)(vite@5.3.4(@types/node@22.7.8)(sass@1.77.8)(terser@5.31.3)) + vitest: + specifier: ^3.0.2 + version: 3.0.5(@types/debug@4.1.12)(@types/node@22.7.8)(jsdom@26.0.0)(sass@1.77.8)(terser@5.31.3) + packages/web: dependencies: '@chakra-ui/anatomy': @@ -298,7 +328,7 @@ importers: version: 2.1.1(@chakra-ui/system@2.6.1(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@emotion/styled@11.11.0(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(react@18.3.1))(react@18.3.1))(react@18.3.1) '@chakra-ui/next-js': specifier: 2.1.5 - version: 2.1.5(@chakra-ui/react@2.8.1(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@emotion/styled@11.11.0(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(framer-motion@9.1.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(next@14.2.5(@babel/core@7.24.9)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.8))(react@18.3.1) + version: 2.1.5(@chakra-ui/react@2.8.1(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@emotion/styled@11.11.0(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(framer-motion@9.1.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(next@14.2.21(@babel/core@7.24.9)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.8))(react@18.3.1) '@chakra-ui/react': specifier: 2.8.1 version: 2.8.1(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@emotion/styled@11.11.0(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(framer-motion@9.1.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -361,7 +391,7 @@ importers: version: 4.17.21 next-i18next: specifier: 15.3.0 - version: 15.3.0(i18next@23.11.5)(next@14.2.5(@babel/core@7.24.9)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.8))(react-i18next@14.1.2(i18next@23.11.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1) + version: 15.3.0(i18next@23.11.5)(next@14.2.21(@babel/core@7.24.9)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.8))(react-i18next@14.1.2(i18next@23.11.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1) papaparse: specifier: ^5.4.1 version: 5.4.1 @@ -419,7 +449,7 @@ importers: version: 2.1.1(@chakra-ui/system@2.6.1(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@emotion/styled@11.11.0(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(react@18.3.1))(react@18.3.1))(react@18.3.1) '@chakra-ui/next-js': specifier: 2.1.5 - version: 2.1.5(@chakra-ui/react@2.8.1(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@emotion/styled@11.11.0(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(framer-motion@9.1.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(next@14.2.5(@babel/core@7.24.9)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.8))(react@18.3.1) + version: 2.1.5(@chakra-ui/react@2.8.1(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@emotion/styled@11.11.0(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(framer-motion@9.1.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(next@14.2.21(@babel/core@7.24.9)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.8))(react@18.3.1) '@chakra-ui/react': specifier: 2.8.1 version: 2.8.1(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@emotion/styled@11.11.0(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(framer-motion@9.1.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -469,8 +499,8 @@ importers: specifier: ^3.7.11 version: 3.8.0(react@18.3.1) axios: - specifier: ^1.5.1 - version: 1.7.2 + specifier: ^1.8.2 + version: 1.8.2 date-fns: specifier: 2.30.0 version: 2.30.0 @@ -523,11 +553,11 @@ importers: specifier: ^4.0.1 version: 4.0.2 next: - specifier: 14.2.5 - version: 14.2.5(@babel/core@7.24.9)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.8) + specifier: 14.2.21 + version: 14.2.21(@babel/core@7.24.9)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.8) next-i18next: specifier: 15.3.0 - version: 15.3.0(i18next@23.11.5)(next@14.2.5(@babel/core@7.24.9)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.8))(react-i18next@14.1.2(i18next@23.11.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1) + version: 15.3.0(i18next@23.11.5)(next@14.2.21(@babel/core@7.24.9)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.8))(react-i18next@14.1.2(i18next@23.11.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1) nextjs-node-loader: specifier: ^1.1.5 version: 1.1.5(webpack@5.92.1) @@ -590,7 +620,7 @@ importers: version: 1.77.8 ts-jest: specifier: ^29.1.0 - version: 29.2.2(@babel/core@7.24.9)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.24.9))(jest@29.7.0(@types/node@20.14.11)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@20.14.11)(typescript@5.5.3)))(typescript@5.5.3) + version: 29.2.2(@babel/core@7.24.9)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.24.9))(jest@29.7.0(@types/node@20.14.11)(babel-plugin-macros@3.1.0))(typescript@5.5.3) use-context-selector: specifier: ^1.4.4 version: 1.4.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(scheduler@0.23.2) @@ -603,7 +633,7 @@ importers: version: 9.0.3 '@shelf/jest-mongodb': specifier: ^4.3.2 - version: 4.3.2(jest-environment-node@29.7.0)(mongodb@6.9.0(socks@2.8.3)) + version: 4.3.2(jest-environment-node@29.7.0)(mongodb@6.13.1(socks@2.8.3)) '@svgr/webpack': specifier: ^6.5.1 version: 6.5.1 @@ -645,13 +675,16 @@ importers: version: 14.2.3(eslint@8.56.0)(typescript@5.5.3) mockingoose: specifier: ^2.16.2 - version: 2.16.2(mongoose@7.8.2) + version: 2.16.2(mongoose@8.10.2(socks@2.8.3)) mongodb-memory-server: specifier: ^10.0.0 version: 10.1.0(socks@2.8.3) typescript: specifier: ^5.1.3 version: 5.5.3 + vitest: + specifier: ^3.0.2 + version: 3.0.5(@types/debug@4.1.12)(@types/node@20.14.11)(jsdom@26.0.0)(sass@1.77.8)(terser@5.31.3) projects/sandbox: dependencies: @@ -733,7 +766,7 @@ importers: version: 6.3.4 ts-jest: specifier: ^29.1.0 - version: 29.2.2(@babel/core@7.24.9)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.24.9))(jest@29.7.0(@types/node@20.14.11)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@20.14.11)(typescript@5.5.3)))(typescript@5.5.3) + version: 29.2.2(@babel/core@7.24.9)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.24.9))(jest@29.7.0(@types/node@20.14.11)(babel-plugin-macros@3.1.0))(typescript@5.5.3) ts-loader: specifier: ^9.4.3 version: 9.5.1(typescript@5.5.3)(webpack@5.92.1) @@ -792,6 +825,9 @@ packages: peerDependencies: openapi-types: '>=7' + '@asamuzakjp/css-color@2.8.3': + resolution: {integrity: sha512-GIc76d9UI1hCvOATjZPyHFmE5qhRccp3/zGfMPapK3jBi+yocEzp6BBB0UnfRYP9NP4FANqUZYb0hnfs3TM3hw==} + '@azure/abort-controller@2.1.2': resolution: {integrity: sha512-nBrLsEWm4J2u5LpAPjxADTlq3trDgVZZXHNKabeXZtpq3d3AbN/KGO82R87rdDz5/lYB024rtEf10/q0urNgsA==} engines: {node: '>=18.0.0'} @@ -864,14 +900,26 @@ packages: resolution: {integrity: sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==} engines: {node: '>=6.9.0'} + '@babel/code-frame@7.26.2': + resolution: {integrity: sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==} + engines: {node: '>=6.9.0'} + '@babel/compat-data@7.24.9': resolution: {integrity: sha512-e701mcfApCJqMMueQI0Fb68Amflj83+dvAvHawoBpAz+GDjCIyGHzNwnefjsWJ3xiYAqqiQFoWbspGYBdb2/ng==} engines: {node: '>=6.9.0'} + '@babel/compat-data@7.26.8': + resolution: {integrity: sha512-oH5UPLMWR3L2wEFLnFJ1TZXqHufiTKAiLfqw5zkhS4dKXLJ10yVztfil/twG8EDTA4F/tvVNw9nOl4ZMslB8rQ==} + engines: {node: '>=6.9.0'} + '@babel/core@7.24.9': resolution: {integrity: sha512-5e3FI4Q3M3Pbr21+5xJwCv6ZT6KmGkI0vw3Tozy5ODAQFTIWe37iT8Cr7Ice2Ntb+M3iSKCEWMB1MBgKrW3whg==} engines: {node: '>=6.9.0'} + '@babel/core@7.26.8': + resolution: {integrity: sha512-l+lkXCHS6tQEc5oUpK28xBOZ6+HwaH7YwoYQbLFiYb4nS2/l1tKnZEtEWkD0GuiYdvArf9qBS0XlQGXzPMsNqQ==} + engines: {node: '>=6.9.0'} + '@babel/generator@7.24.10': resolution: {integrity: sha512-o9HBZL1G2129luEUlG1hB4N/nlYNWHnpwlND9eOMclRqqu1YDy2sSYVCFUZwl8I1Gxh+QSRrP2vD7EpUmFVXxg==} engines: {node: '>=6.9.0'} @@ -880,6 +928,10 @@ packages: resolution: {integrity: sha512-VPC82gr1seXOpkjAAKoLhP50vx4vGNlF4msF64dSFq1P8RfB+QAuJWGHPXXPc8QyfVWwwB/TNNU4+ayZmHNbZw==} engines: {node: '>=6.9.0'} + '@babel/generator@7.26.8': + resolution: {integrity: sha512-ef383X5++iZHWAXX0SXQR6ZyQhw/0KtTkrTz61WXRhFM6dhpHulO/RJz79L8S6ugZHJkOOkUrUdxgdF2YiPFnA==} + engines: {node: '>=6.9.0'} + '@babel/helper-annotate-as-pure@7.24.7': resolution: {integrity: sha512-BaDeOonYvhdKw+JoMVkAixAAJzG2jVPIwWoKBPdYuY9b452e2rPuI9QPYh3KpofZ3pW2akOmwZLOiOsHMiqRAg==} engines: {node: '>=6.9.0'} @@ -892,6 +944,10 @@ packages: resolution: {integrity: sha512-oU+UoqCHdp+nWVDkpldqIQL/i/bvAv53tRqLG/s+cOXxe66zOYLU7ar/Xs3LdmBihrUMEUhwu6dMZwbNOYDwvw==} engines: {node: '>=6.9.0'} + '@babel/helper-compilation-targets@7.26.5': + resolution: {integrity: sha512-IXuyn5EkouFJscIDuFF5EsiSolseme1s0CZB+QxVugqJLYmKdxI1VfIBOst0SUu4rnk2Z7kqTwmoO1lp3HIfnA==} + engines: {node: '>=6.9.0'} + '@babel/helper-create-class-features-plugin@7.24.8': resolution: {integrity: sha512-4f6Oqnmyp2PP3olgUMmOwC3akxSm5aBYraQ6YDdKy7NcAMkDECHWG0DEnV6M2UAkERgIBhYt8S27rURPg7SxWA==} engines: {node: '>=6.9.0'} @@ -929,12 +985,22 @@ packages: resolution: {integrity: sha512-8AyH3C+74cgCVVXow/myrynrAGv+nTVg5vKu2nZph9x7RcRwzmh0VFallJuFTZ9mx6u4eSdXZfcOzSqTUm0HCA==} engines: {node: '>=6.9.0'} + '@babel/helper-module-imports@7.25.9': + resolution: {integrity: sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==} + engines: {node: '>=6.9.0'} + '@babel/helper-module-transforms@7.24.9': resolution: {integrity: sha512-oYbh+rtFKj/HwBQkFlUzvcybzklmVdVV3UU+mN7n2t/q3yGHbuVdNxyFvSBO1tfvjyArpHNcWMAzsSPdyI46hw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 + '@babel/helper-module-transforms@7.26.0': + resolution: {integrity: sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + '@babel/helper-optimise-call-expression@7.24.7': resolution: {integrity: sha512-jKiTsW2xmWwxT1ixIdfXUZp+P5yURx2suzLZr5Hi64rURpDYdMW0pv+Uf17EYk2Rd428Lx4tLsnjGJzYKDM/6A==} engines: {node: '>=6.9.0'} @@ -943,6 +1009,10 @@ packages: resolution: {integrity: sha512-FFWx5142D8h2Mgr/iPVGH5G7w6jDn4jUSpZTyDnQO0Yn7Ks2Kuz6Pci8H6MPCoUJegd/UZQ3tAvfLCxQSnWWwg==} engines: {node: '>=6.9.0'} + '@babel/helper-plugin-utils@7.26.5': + resolution: {integrity: sha512-RS+jZcRdZdRFzMyr+wcsaqOmld1/EqTghfaBGQQd/WnRdzdlvSZ//kF7U8VQTxf1ynZ4cjUcYgjVGx13ewNPMg==} + engines: {node: '>=6.9.0'} + '@babel/helper-remap-async-to-generator@7.24.7': resolution: {integrity: sha512-9pKLcTlZ92hNZMQfGCHImUpDOlAgkkpqalWEeftW5FBya75k8Li2ilerxkM/uBEj01iBZXcCIB/bwvDYgWyibA==} engines: {node: '>=6.9.0'} @@ -971,14 +1041,26 @@ packages: resolution: {integrity: sha512-pO9KhhRcuUyGnJWwyEgnRJTSIZHiT+vMD0kPeD+so0l7mxkMT19g3pjY9GTnHySck/hDzq+dtW/4VgnMkippsQ==} engines: {node: '>=6.9.0'} + '@babel/helper-string-parser@7.25.9': + resolution: {integrity: sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==} + engines: {node: '>=6.9.0'} + '@babel/helper-validator-identifier@7.24.7': resolution: {integrity: sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==} engines: {node: '>=6.9.0'} + '@babel/helper-validator-identifier@7.25.9': + resolution: {integrity: sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==} + engines: {node: '>=6.9.0'} + '@babel/helper-validator-option@7.24.8': resolution: {integrity: sha512-xb8t9tD1MHLungh/AIoWYN+gVHaB9kwlu8gffXGSt3FFEIT7RjS+xWbc2vUD1UTZdIpKj/ab3rdqJ7ufngyi2Q==} engines: {node: '>=6.9.0'} + '@babel/helper-validator-option@7.25.9': + resolution: {integrity: sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==} + engines: {node: '>=6.9.0'} + '@babel/helper-wrap-function@7.24.7': resolution: {integrity: sha512-N9JIYk3TD+1vq/wn77YnJOqMtfWhNewNE+DJV4puD2X7Ew9J4JvrzrFDfTfyv5EgEXVy9/Wt8QiOErzEmv5Ifw==} engines: {node: '>=6.9.0'} @@ -987,6 +1069,10 @@ packages: resolution: {integrity: sha512-gV2265Nkcz7weJJfvDoAEVzC1e2OTDpkGbEsebse8koXUJUXPsCMi7sRo/+SPMuMZ9MtUPnGwITTnQnU5YjyaQ==} engines: {node: '>=6.9.0'} + '@babel/helpers@7.26.7': + resolution: {integrity: sha512-8NHiL98vsi0mbPQmYAGWwfcFaOy4j2HY49fXJCfuDcdE7fMIsH9a7GdaeXpIBsbT7307WU8KCMp5pUVDNL4f9A==} + engines: {node: '>=6.9.0'} + '@babel/highlight@7.24.7': resolution: {integrity: sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==} engines: {node: '>=6.9.0'} @@ -1001,6 +1087,11 @@ packages: engines: {node: '>=6.0.0'} hasBin: true + '@babel/parser@7.26.8': + resolution: {integrity: sha512-TZIQ25pkSoaKEYYaHbbxkfL36GNsQ6iFiBbeuzAkLnXayKR1yP1zFe+NxuZWWsUyvt8icPU9CCq0sgWGXR1GEw==} + engines: {node: '>=6.0.0'} + hasBin: true + '@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.24.7': resolution: {integrity: sha512-TiT1ss81W80eQsN+722OaeQMY/G4yTb4G9JrqeiDADs3N8lbPMGldWi9x8tyqCW5NLx1Jh2AvkE6r6QvEltMMQ==} engines: {node: '>=6.9.0'} @@ -1384,6 +1475,18 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-react-jsx-self@7.25.9': + resolution: {integrity: sha512-y8quW6p0WHkEhmErnfe58r7x0A70uKphQm8Sp8cV7tjNQwK56sNVK0M73LK3WuYmsuyrftut4xAkjjgU0twaMg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-react-jsx-source@7.25.9': + resolution: {integrity: sha512-+iqjT8xmXhhYv4/uiYd8FNQsraMFZIfxVSqxxVSZP0WbbSAWvBXAul0m/zu+7Vv4O/3WtApy9pmaTMiumEZgfg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-react-jsx@7.24.7': resolution: {integrity: sha512-+Dj06GDZEFRYvclU6k4bme55GKBEWUmByM/eoKuqg4zTNQHiApWRhQph5fxQB2wAEFvRzL1tOEj1RJ19wJrhoA==} engines: {node: '>=6.9.0'} @@ -1510,6 +1613,10 @@ packages: resolution: {integrity: sha512-aOOgh1/5XzKvg1jvVz7AVrx2piJ2XBi227DHmbY6y+bM9H2FlN+IfecYu4Xl0cNiiVejlsCri89LUsbj8vJD9Q==} engines: {node: '>=6.9.0'} + '@babel/template@7.26.8': + resolution: {integrity: sha512-iNKaX3ZebKIsCvJ+0jd6embf+Aulaa3vNBqZ41kM7iTWjx5qzWKXGHiJUW3+nTpQ18SG11hdF8OAzKrpXkb96Q==} + engines: {node: '>=6.9.0'} + '@babel/traverse@7.24.8': resolution: {integrity: sha512-t0P1xxAPzEDcEPmjprAQq19NWum4K0EQPjMwZQZbHt+GiZqvjCHjj755Weq1YRPVzBI+3zSfvScfpnuIecVFJQ==} engines: {node: '>=6.9.0'} @@ -1518,6 +1625,10 @@ packages: resolution: {integrity: sha512-9Vrcx5ZW6UwK5tvqsj0nGpp/XzqthkT0dqIc9g1AdtygFToNtTF67XzYS//dm+SAK9cp3B9R4ZO/46p63SCjlQ==} engines: {node: '>=6.9.0'} + '@babel/traverse@7.26.8': + resolution: {integrity: sha512-nic9tRkjYH0oB2dzr/JoGIm+4Q6SuYeLEiIiZDwBscRMYFJ+tMAz98fuel9ZnbXViA2I0HVSSRRK8DW5fjXStA==} + engines: {node: '>=6.9.0'} + '@babel/types@7.24.9': resolution: {integrity: sha512-xm8XrMKz0IlUdocVbYJe0Z9xEgidU7msskG8BbhnTPK/HZ2z/7FP7ykqPgrUH+C+r414mNfNWam1f2vqOjqjYQ==} engines: {node: '>=6.9.0'} @@ -1526,12 +1637,20 @@ packages: resolution: {integrity: sha512-/l42B1qxpG6RdfYf343Uw1vmDjeNhneUXtzhojE7pDgfpEypmRhI6j1kr17XCVv4Cgl9HdAiQY2x0GwKm7rWCw==} engines: {node: '>=6.9.0'} + '@babel/types@7.26.8': + resolution: {integrity: sha512-eUuWapzEGWFEpHFxgEaBG8e3n6S8L3MSu0oda755rOfabWPnh0Our1AozNFVUxGFIhbKgd1ksprsoDGMinTOTA==} + engines: {node: '>=6.9.0'} + '@bany/curl-to-json@1.2.8': resolution: {integrity: sha512-hPt9KUM2sGZ5Ojx3O9utjzUgjRZI3CZPAlLf+cRY9EUzVs7tWt1OpA0bhEUTX2PEEkOeyZ6sC0tAQMOHh9ld+Q==} '@bcoe/v8-coverage@0.2.3': resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==} + '@bcoe/v8-coverage@1.0.2': + resolution: {integrity: sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==} + engines: {node: '>=18'} + '@braintree/sanitize-url@6.0.4': resolution: {integrity: sha512-s3jaWicZd0pkP0jf5ysyHUI/RE7MHos6qlToFcGWXVp+ykHOy77OUMrfbgJ9it2C5bow7OIQwYYaHjk9XlBQ2A==} @@ -2038,6 +2157,34 @@ packages: resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} engines: {node: '>=12'} + '@csstools/color-helpers@5.0.1': + resolution: {integrity: sha512-MKtmkA0BX87PKaO1NFRTFH+UnkgnmySQOvNxJubsadusqPEC2aJ9MOQiMceZJJ6oitUl/i0L6u0M1IrmAOmgBA==} + engines: {node: '>=18'} + + '@csstools/css-calc@2.1.1': + resolution: {integrity: sha512-rL7kaUnTkL9K+Cvo2pnCieqNpTKgQzy5f+N+5Iuko9HAoasP+xgprVh7KN/MaJVvVL1l0EzQq2MoqBHKSrDrag==} + engines: {node: '>=18'} + peerDependencies: + '@csstools/css-parser-algorithms': ^3.0.4 + '@csstools/css-tokenizer': ^3.0.3 + + '@csstools/css-color-parser@3.0.7': + resolution: {integrity: sha512-nkMp2mTICw32uE5NN+EsJ4f5N+IGFeCFu4bGpiKgb2Pq/7J/MpyLBeQ5ry4KKtRFZaYs6sTmcMYrSRIyj5DFKA==} + engines: {node: '>=18'} + peerDependencies: + '@csstools/css-parser-algorithms': ^3.0.4 + '@csstools/css-tokenizer': ^3.0.3 + + '@csstools/css-parser-algorithms@3.0.4': + resolution: {integrity: sha512-Up7rBoV77rv29d3uKHUIVubz1BTcgyUK72IvCQAbfbMv584xHcGKCKbWh7i8hPrRJ7qU4Y8IO3IY9m+iTB7P3A==} + engines: {node: '>=18'} + peerDependencies: + '@csstools/css-tokenizer': ^3.0.3 + + '@csstools/css-tokenizer@3.0.3': + resolution: {integrity: sha512-UJnjoFsmxfKUdNYdWgOB0mWUypuLvAfQPH1+pyvRJs6euowbFkFC6P13w1l8mJyi3vxYMxc9kld5jZEGRQs6bw==} + engines: {node: '>=18'} + '@dabh/diagnostics@2.0.3': resolution: {integrity: sha512-hrlQOIi7hAfzsMqlGSFyVucrx38O+j6wiGOf//H2ecvIEqYN4ADBSS2iLMh5UFyDunCNniUIPk/q3riFv45xRA==} @@ -2401,6 +2548,7 @@ packages: '@faker-js/faker@9.0.3': resolution: {integrity: sha512-lWrrK4QNlFSU+13PL9jMbMKLJYXDFu3tQfayBsMXX7KL/GiQeqfB1CzHkqD5UHBUtPAuPo6XwGbMFNdVMZObRA==} engines: {node: '>=18.0.0', npm: '>=9.0.0'} + deprecated: Please update to a newer version '@fastify/accept-negotiator@1.1.0': resolution: {integrity: sha512-OIHZrb2ImZ7XG85HXOONLcJWGosv7sIvM2ifAPQVhg9Lv7qdmMBNVaai4QTdyuaqbKM5eO6sLSQOYI7wEQeCJQ==} @@ -2573,14 +2721,14 @@ packages: '@jsdevtools/ono@7.1.3': resolution: {integrity: sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==} - '@jsep-plugin/assignment@1.2.1': - resolution: {integrity: sha512-gaHqbubTi29aZpVbBlECRpmdia+L5/lh2BwtIJTmtxdbecEyyX/ejAOg7eQDGNvGOUmPY7Z2Yxdy9ioyH/VJeA==} + '@jsep-plugin/assignment@1.3.0': + resolution: {integrity: sha512-VVgV+CXrhbMI3aSusQyclHkenWSAm95WaiKrMxRFam3JSUiIaQjoMIw2sEs/OX4XifnqeQUN4DYbJjlA8EfktQ==} engines: {node: '>= 10.16.0'} peerDependencies: jsep: ^0.4.0||^1.0.0 - '@jsep-plugin/regex@1.0.3': - resolution: {integrity: sha512-XfZgry4DwEZvSFtS/6Y+R48D7qJYJK6R9/yJFyUFHCIUMEEHuJ4X95TDgJp5QkmzfLYvapMPzskV5HpIDrREug==} + '@jsep-plugin/regex@1.0.4': + resolution: {integrity: sha512-q7qL4Mgjs1vByCaTnDFcBnV9HS7GVPJX5vyVoCgZHNSC9rjwIlmbXG5sUuorR5ndfHAIlJ8pVStxvjXHbNvtUg==} engines: {node: '>= 10.16.0'} peerDependencies: jsep: ^0.4.0||^1.0.0 @@ -2841,62 +2989,62 @@ packages: '@nestjs/platform-express': optional: true - '@next/env@14.2.5': - resolution: {integrity: sha512-/zZGkrTOsraVfYjGP8uM0p6r0BDT6xWpkjdVbcz66PJVSpwXX3yNiRycxAuDfBKGWBrZBXRuK/YVlkNgxHGwmA==} + '@next/env@14.2.21': + resolution: {integrity: sha512-lXcwcJd5oR01tggjWJ6SrNNYFGuOOMB9c251wUNkjCpkoXOPkDeF/15c3mnVlBqrW4JJXb2kVxDFhC4GduJt2A==} '@next/eslint-plugin-next@14.2.3': resolution: {integrity: sha512-L3oDricIIjgj1AVnRdRor21gI7mShlSwU/1ZGHmqM3LzHhXXhdkrfeNY5zif25Bi5Dd7fiJHsbhoZCHfXYvlAw==} - '@next/swc-darwin-arm64@14.2.5': - resolution: {integrity: sha512-/9zVxJ+K9lrzSGli1///ujyRfon/ZneeZ+v4ptpiPoOU+GKZnm8Wj8ELWU1Pm7GHltYRBklmXMTUqM/DqQ99FQ==} + '@next/swc-darwin-arm64@14.2.21': + resolution: {integrity: sha512-HwEjcKsXtvszXz5q5Z7wCtrHeTTDSTgAbocz45PHMUjU3fBYInfvhR+ZhavDRUYLonm53aHZbB09QtJVJj8T7g==} engines: {node: '>= 10'} cpu: [arm64] os: [darwin] - '@next/swc-darwin-x64@14.2.5': - resolution: {integrity: sha512-vXHOPCwfDe9qLDuq7U1OYM2wUY+KQ4Ex6ozwsKxp26BlJ6XXbHleOUldenM67JRyBfVjv371oneEvYd3H2gNSA==} + '@next/swc-darwin-x64@14.2.21': + resolution: {integrity: sha512-TSAA2ROgNzm4FhKbTbyJOBrsREOMVdDIltZ6aZiKvCi/v0UwFmwigBGeqXDA97TFMpR3LNNpw52CbVelkoQBxA==} engines: {node: '>= 10'} cpu: [x64] os: [darwin] - '@next/swc-linux-arm64-gnu@14.2.5': - resolution: {integrity: sha512-vlhB8wI+lj8q1ExFW8lbWutA4M2ZazQNvMWuEDqZcuJJc78iUnLdPPunBPX8rC4IgT6lIx/adB+Cwrl99MzNaA==} + '@next/swc-linux-arm64-gnu@14.2.21': + resolution: {integrity: sha512-0Dqjn0pEUz3JG+AImpnMMW/m8hRtl1GQCNbO66V1yp6RswSTiKmnHf3pTX6xMdJYSemf3O4Q9ykiL0jymu0TuA==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] - '@next/swc-linux-arm64-musl@14.2.5': - resolution: {integrity: sha512-NpDB9NUR2t0hXzJJwQSGu1IAOYybsfeB+LxpGsXrRIb7QOrYmidJz3shzY8cM6+rO4Aojuef0N/PEaX18pi9OA==} + '@next/swc-linux-arm64-musl@14.2.21': + resolution: {integrity: sha512-Ggfw5qnMXldscVntwnjfaQs5GbBbjioV4B4loP+bjqNEb42fzZlAaK+ldL0jm2CTJga9LynBMhekNfV8W4+HBw==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] - '@next/swc-linux-x64-gnu@14.2.5': - resolution: {integrity: sha512-8XFikMSxWleYNryWIjiCX+gU201YS+erTUidKdyOVYi5qUQo/gRxv/3N1oZFCgqpesN6FPeqGM72Zve+nReVXQ==} + '@next/swc-linux-x64-gnu@14.2.21': + resolution: {integrity: sha512-uokj0lubN1WoSa5KKdThVPRffGyiWlm/vCc/cMkWOQHw69Qt0X1o3b2PyLLx8ANqlefILZh1EdfLRz9gVpG6tg==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - '@next/swc-linux-x64-musl@14.2.5': - resolution: {integrity: sha512-6QLwi7RaYiQDcRDSU/os40r5o06b5ue7Jsk5JgdRBGGp8l37RZEh9JsLSM8QF0YDsgcosSeHjglgqi25+m04IQ==} + '@next/swc-linux-x64-musl@14.2.21': + resolution: {integrity: sha512-iAEBPzWNbciah4+0yI4s7Pce6BIoxTQ0AGCkxn/UBuzJFkYyJt71MadYQkjPqCQCJAFQ26sYh7MOKdU+VQFgPg==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - '@next/swc-win32-arm64-msvc@14.2.5': - resolution: {integrity: sha512-1GpG2VhbspO+aYoMOQPQiqc/tG3LzmsdBH0LhnDS3JrtDx2QmzXe0B6mSZZiN3Bq7IOMXxv1nlsjzoS1+9mzZw==} + '@next/swc-win32-arm64-msvc@14.2.21': + resolution: {integrity: sha512-plykgB3vL2hB4Z32W3ktsfqyuyGAPxqwiyrAi2Mr8LlEUhNn9VgkiAl5hODSBpzIfWweX3er1f5uNpGDygfQVQ==} engines: {node: '>= 10'} cpu: [arm64] os: [win32] - '@next/swc-win32-ia32-msvc@14.2.5': - resolution: {integrity: sha512-Igh9ZlxwvCDsu6438FXlQTHlRno4gFpJzqPjSIBZooD22tKeI4fE/YMRoHVJHmrQ2P5YL1DoZ0qaOKkbeFWeMg==} + '@next/swc-win32-ia32-msvc@14.2.21': + resolution: {integrity: sha512-w5bacz4Vxqrh06BjWgua3Yf7EMDb8iMcVhNrNx8KnJXt8t+Uu0Zg4JHLDL/T7DkTCEEfKXO/Er1fcfWxn2xfPA==} engines: {node: '>= 10'} cpu: [ia32] os: [win32] - '@next/swc-win32-x64-msvc@14.2.5': - resolution: {integrity: sha512-tEQ7oinq1/CjSG9uSTerca3v4AZ+dFa+4Yu6ihaG8Ud8ddqLQgFGcnwYls13H5X5CPDPZJdYxyeMui6muOLd4g==} + '@next/swc-win32-x64-msvc@14.2.21': + resolution: {integrity: sha512-sT6+llIkzpsexGYZq8cjjthRyRGe5cJVhqh12FmlbxHqna6zsDDK8UNaV7g41T6atFHCJUPeLb3uyAwrBwy0NA==} engines: {node: '>= 10'} cpu: [x64] os: [win32] @@ -3293,6 +3441,25 @@ packages: '@tediousjs/connection-string@0.5.0': resolution: {integrity: sha512-7qSgZbincDDDFyRweCIEvZULFAw5iz/DeunhvuxpL31nfntX3P4Yd4HkHBRg9H8CdqY1e5WFN1PZIz/REL9MVQ==} + '@testing-library/dom@10.4.0': + resolution: {integrity: sha512-pemlzrSESWbdAloYml3bAJMEfNh1Z7EduzqPKprCH5S341frlpYnUEW0H72dLxa6IsYr+mPno20GiSm+h9dEdQ==} + engines: {node: '>=18'} + + '@testing-library/react@16.2.0': + resolution: {integrity: sha512-2cSskAvA1QNtKc8Y9VJQRv0tm3hLVgxRGDB+KYhIaPQJ1I+RHbhIXcM+zClKXzMes/wshsMVzf4B9vS4IZpqDQ==} + engines: {node: '>=18'} + peerDependencies: + '@testing-library/dom': ^10.0.0 + '@types/react': ^18.0.0 || ^19.0.0 + '@types/react-dom': ^18.0.0 || ^19.0.0 + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@tokenizer/token@0.3.0': resolution: {integrity: sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==} @@ -3315,6 +3482,9 @@ packages: '@tybys/wasm-util@0.8.3': resolution: {integrity: sha512-Z96T/L6dUFFxgFJ+pQtkPpne9q7i6kIPYCFnQBHSgSPV9idTsKfIhCss0h5iM9irweZCatkrdeP8yi5uM1eX6Q==} + '@types/aria-query@5.0.4': + resolution: {integrity: sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==} + '@types/babel__core@7.20.5': resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} @@ -3462,6 +3632,9 @@ packages: '@types/formidable@2.0.6': resolution: {integrity: sha512-L4HcrA05IgQyNYJj6kItuIkXrInJvsXTPC5B1i64FggWKKqSL+4hgt7asiSNva75AoLQjq29oPxFfU4GAQ6Z2w==} + '@types/gensync@1.0.4': + resolution: {integrity: sha512-C3YYeRQWp2fmq9OryX+FoDy8nXS6scQ7dPptD8LnFDAUNcKWJjXQKDNJD3HVm+kOUsXhTOkpi69vI4EuAr95bA==} + '@types/geojson@7946.0.14': resolution: {integrity: sha512-WCfD5Ht3ZesJUsONdhvm84dmzWOiOzOAqOncN0++w0lBw1o8OuDNJF2McvvCef/yBqb/HYRahp1BYtODFQ8bRg==} @@ -3718,21 +3891,65 @@ packages: '@ungap/structured-clone@1.2.0': resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==} + '@vitejs/plugin-react@4.3.4': + resolution: {integrity: sha512-SCCPBJtYLdE8PX/7ZQAs1QAZ8Jqwih+0VBLum1EGqmCCQal+MIUqLCzj3ZUy8ufbC0cAM4LRlSTm7IQJwWT4ug==} + engines: {node: ^14.18.0 || >=16.0.0} + peerDependencies: + vite: ^4.2.0 || ^5.0.0 || ^6.0.0 + + '@vitest/coverage-v8@3.0.5': + resolution: {integrity: sha512-zOOWIsj5fHh3jjGwQg+P+J1FW3s4jBu1Zqga0qW60yutsBtqEqNEJKWYh7cYn1yGD+1bdPsPdC/eL4eVK56xMg==} + peerDependencies: + '@vitest/browser': 3.0.5 + vitest: 3.0.5 + peerDependenciesMeta: + '@vitest/browser': + optional: true + '@vitest/expect@1.6.0': resolution: {integrity: sha512-ixEvFVQjycy/oNgHjqsL6AZCDduC+tflRluaHIzKIsdbzkLn2U/iBnVeJwB6HsIjQBdfMR8Z0tRxKUsvFJEeWQ==} + '@vitest/expect@3.0.5': + resolution: {integrity: sha512-nNIOqupgZ4v5jWuQx2DSlHLEs7Q4Oh/7AYwNyE+k0UQzG7tSmjPXShUikn1mpNGzYEN2jJbTvLejwShMitovBA==} + + '@vitest/mocker@3.0.5': + resolution: {integrity: sha512-CLPNBFBIE7x6aEGbIjaQAX03ZZlBMaWwAjBdMkIf/cAn6xzLTiM3zYqO/WAbieEjsAZir6tO71mzeHZoodThvw==} + peerDependencies: + msw: ^2.4.9 + vite: ^5.0.0 || ^6.0.0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + + '@vitest/pretty-format@3.0.5': + resolution: {integrity: sha512-CjUtdmpOcm4RVtB+up8r2vVDLR16Mgm/bYdkGFe3Yj/scRfCpbSi2W/BDSDcFK7ohw8UXvjMbOp9H4fByd/cOA==} + '@vitest/runner@1.6.0': resolution: {integrity: sha512-P4xgwPjwesuBiHisAVz/LSSZtDjOTPYZVmNAnpHHSR6ONrf8eCJOFRvUwdHn30F5M1fxhqtl7QZQUk2dprIXAg==} + '@vitest/runner@3.0.5': + resolution: {integrity: sha512-BAiZFityFexZQi2yN4OX3OkJC6scwRo8EhRB0Z5HIGGgd2q+Nq29LgHU/+ovCtd0fOfXj5ZI6pwdlUmC5bpi8A==} + '@vitest/snapshot@1.6.0': resolution: {integrity: sha512-+Hx43f8Chus+DCmygqqfetcAZrDJwvTj0ymqjQq4CvmpKFSTVteEOBzCusu1x2tt4OJcvBflyHUE0DZSLgEMtQ==} + '@vitest/snapshot@3.0.5': + resolution: {integrity: sha512-GJPZYcd7v8QNUJ7vRvLDmRwl+a1fGg4T/54lZXe+UOGy47F9yUfE18hRCtXL5aHN/AONu29NGzIXSVFh9K0feA==} + '@vitest/spy@1.6.0': resolution: {integrity: sha512-leUTap6B/cqi/bQkXUu6bQV5TZPx7pmMBKBQiI0rJA8c3pB56ZsaTbREnF7CJfmvAS4V2cXIBAh/3rVwrrCYgw==} + '@vitest/spy@3.0.5': + resolution: {integrity: sha512-5fOzHj0WbUNqPK6blI/8VzZdkBlQLnT25knX0r4dbZI9qoZDf3qAdjoMmDcLG5A83W6oUUFJgUd0EYBc2P5xqg==} + '@vitest/utils@1.6.0': resolution: {integrity: sha512-21cPiuGMoMZwiOHa2i4LXkMkMkCGzA+MVFV70jRwHo95dL4x/ts5GZhML1QWuy7yfp3WzK3lRvZi3JnXTYqrBw==} + '@vitest/utils@3.0.5': + resolution: {integrity: sha512-N9AX0NUoUtVwKwy21JtwzaqR5L5R5A99GAbrHfCCXK1lp593i/3AZAXhSP43wRQuxYsflrdzEfXZFo1reR1Nkg==} + '@vue/compiler-core@3.4.32': resolution: {integrity: sha512-8tCVWkkLe/QCWIsrIvExUGnhYCAOroUs5dzhSoKL5w4MJS8uIYiou+pOPSVIOALOQ80B0jBs+Ri+kd5+MBnCDw==} @@ -3874,6 +4091,10 @@ packages: resolution: {integrity: sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==} engines: {node: '>= 14'} + agent-base@7.1.3: + resolution: {integrity: sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==} + engines: {node: '>= 14'} + agentkeepalive@4.5.0: resolution: {integrity: sha512-5GG/5IbQQpC9FpkRGsSvZI5QYeSCzlJHdpBQntCsuTOxhKD8lqKhrleg2Yi7yvMIf82Ycmmqln9U8V9qwEiJew==} engines: {node: '>= 8.0.0'} @@ -3993,6 +4214,9 @@ packages: aria-query@5.1.3: resolution: {integrity: sha512-R5iJ5lkuHybztUfuOAznmboyjWq8O6sqNqtK7CLOqdydi54VNbORp49mb14KbWgG1QD3JFO9hJdZ+y4KutfdOQ==} + aria-query@5.3.0: + resolution: {integrity: sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==} + array-buffer-byte-length@1.0.1: resolution: {integrity: sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg==} engines: {node: '>= 0.4'} @@ -4044,6 +4268,10 @@ packages: assertion-error@1.1.0: resolution: {integrity: sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==} + assertion-error@2.0.1: + resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} + engines: {node: '>=12'} + ast-types-flow@0.0.8: resolution: {integrity: sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==} @@ -4081,11 +4309,8 @@ packages: resolution: {integrity: sha512-QbUdXJVTpvUTHU7871ppZkdOLBeGUKBQWHkHrvN2V9IQWGMt61zf3B45BtzjxEJzYuj0JBjBZP/hmYS/R9pmAw==} engines: {node: '>=4'} - axios@1.7.2: - resolution: {integrity: sha512-2A8QhOMrbomlDuiLeK9XibIBzuHeRcqqNOHp0Cyp5EoJ1IFDh+XZH3A6BkXtv0K4gFGCI0Y4BM7B1wOEi0Rmgw==} - - axios@1.7.7: - resolution: {integrity: sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==} + axios@1.8.2: + resolution: {integrity: sha512-ls4GYBm5aig9vWx8AWDSGLpnpDQRtWAfrjU+EuytuODrFBkqesN2RkOQCBzrA1RQNHw1SmRMSDDDSwzNAYQ6Rg==} axobject-query@3.1.1: resolution: {integrity: sha512-goKlv8DZrK9hUh975fnHzhNIO4jUnFCfv/dszV5VwUGDFjI6vQ2VwoyjYjYNEbBE8AH87TduWP5uyDR1D+Iteg==} @@ -4193,6 +4418,11 @@ packages: engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true + browserslist@4.24.4: + resolution: {integrity: sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + bs-logger@0.2.6: resolution: {integrity: sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==} engines: {node: '>= 6'} @@ -4204,9 +4434,14 @@ packages: resolution: {integrity: sha512-ix0EwukN2EpC0SRWIj/7B5+A6uQMQy6KMREI9qQqvgpkV2frH63T0UDVd1SYedL6dNCmDBYB3QtXi4ISk9YT+g==} engines: {node: '>=14.20.1'} + bson@6.10.3: + resolution: {integrity: sha512-MTxGsqgYTwfshYWTRdmZRC+M7FnG1b4y7RO7p2k3X24Wq0yv1m77Wsj0BzlPzd/IowgESfsruQCUToa7vbOpPQ==} + engines: {node: '>=16.20.1'} + bson@6.8.0: resolution: {integrity: sha512-iOJg8pr7wq2tg/zSlCCHMi3hMm5JTOxLTagf3zxhcenHsFp+c6uOs6K7W5UE7A4QIJGtqh/ZovFNMP4mOPJynQ==} engines: {node: '>=16.20.1'} + deprecated: a critical bug affecting only useBigInt64=true deserialization usage is fixed in bson@6.10.3 buffer-alloc-unsafe@1.1.0: resolution: {integrity: sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg==} @@ -4248,6 +4483,10 @@ packages: resolution: {integrity: sha512-B+L5iIa9mgcjLbliir2th36yEwPftrzteHYujzsx3dFP/31GCHcIeS8f5MGd80odLOjaOvSpU3EEAmRQptkxLQ==} engines: {node: ^16.14.0 || >=18.0.0} + call-bind-apply-helpers@1.0.2: + resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} + engines: {node: '>= 0.4'} + call-bind@1.0.7: resolution: {integrity: sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==} engines: {node: '>= 0.4'} @@ -4271,8 +4510,11 @@ packages: resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} engines: {node: '>=10'} - caniuse-lite@1.0.30001669: - resolution: {integrity: sha512-DlWzFDJqstqtIVx1zeSpIMLjunf5SmwOw0N2Ck/QSQdS8PLS4+9HrLaYei4w8BIAL7IB/UEDu889d8vhCTPA0w==} + caniuse-lite@1.0.30001703: + resolution: {integrity: sha512-kRlAGTRWgPsOj7oARC9m1okJEXdL/8fekFVcxA8Hl7GH4r/sN4OJn/i6Flde373T50KS7Y37oFbMwlE8+F42kQ==} + + caniuse-lite@1.0.30001698: + resolution: {integrity: sha512-xJ3km2oiG/MbNU8G6zIq6XRZ6HtAOVXsbOrP/blGazi52kc5Yy7b6sDA5O+FbROzRrV7BSTllLHuNvmawYUJjw==} canvas@2.11.2: resolution: {integrity: sha512-ItanGBMrmRV7Py2Z+Xhs7cT+FNt5K0vPL4p9EZ/UX/Mu7hFbkxSjKF2KVtPwX7UYWp7dRKnrTvReflgrItJbdw==} @@ -4285,6 +4527,10 @@ packages: resolution: {integrity: sha512-13sOfMv2+DWduEU+/xbun3LScLoqN17nBeTLUsmDfKdoiC1fr0n9PU4guu4AhRcOVFk/sW8LyZWHuhWtQZiF+g==} engines: {node: '>=4'} + chai@5.1.2: + resolution: {integrity: sha512-aGtmf24DW6MLHHG5gCx4zaI3uBq3KRtxeVs0DjFH6Z0rDNbsvTxFASFvdj79pxjxZ8/5u3PIiN3IwEIQkiiuPw==} + engines: {node: '>=12'} + chalk@2.4.2: resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} engines: {node: '>=4'} @@ -4332,6 +4578,10 @@ packages: check-error@1.0.3: resolution: {integrity: sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==} + check-error@2.1.1: + resolution: {integrity: sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==} + engines: {node: '>= 16'} + cheerio-select@2.1.0: resolution: {integrity: sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==} @@ -4640,6 +4890,10 @@ packages: resolution: {integrity: sha512-wvlcdIbf6pwKEk7vHj8/Bkc0B4ylXZruLvOgs9doS5eOsOpuodOV2zJChSpkp+pRpYQLQMeF04nr3Z68Sta9jA==} engines: {node: '>=8.0.0'} + cssstyle@4.2.1: + resolution: {integrity: sha512-9+vem03dMXG7gDmZ62uqmRiMRNtinIZ9ZyuF6BdxzfOD+FdN5hretzynkn0ReS2DO2GSw76RWHs0UmJPI2zUjw==} + engines: {node: '>=18'} + csstype@3.1.3: resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} @@ -4797,6 +5051,10 @@ packages: damerau-levenshtein@1.0.8: resolution: {integrity: sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==} + data-urls@5.0.0: + resolution: {integrity: sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==} + engines: {node: '>=18'} + data-view-buffer@1.0.1: resolution: {integrity: sha512-0lht7OugA5x3iJLOWFhWK/5ehONdprk0ISXqVFn/NFrDu+cuc8iADFrGQz5BnRK7LLU3JmkbXSxaqX+/mXYtUA==} engines: {node: '>= 0.4'} @@ -4862,6 +5120,15 @@ packages: supports-color: optional: true + debug@4.4.0: + resolution: {integrity: sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + decamelize@1.2.0: resolution: {integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==} engines: {node: '>=0.10.0'} @@ -4869,6 +5136,9 @@ packages: decimal.js-light@2.5.1: resolution: {integrity: sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==} + decimal.js@10.5.0: + resolution: {integrity: sha512-8vDa8Qxvr/+d94hSh5P3IJwI5t8/c0KsMp+g8bNw9cY2icONa5aPfvKeieW1WlG0WQYwwhJ7mjui2xtiePQSXw==} + decode-named-character-reference@1.0.2: resolution: {integrity: sha512-O8x12RzrUF8xyVcY0KJowWsmaJxQbmy0/EtnNtHRpsOcT7dFk5W598coHqBVpmWo1oQQfsCqfCmkZN5DJrZVdg==} @@ -4912,6 +5182,10 @@ packages: resolution: {integrity: sha512-SUwdGfqdKOwxCPeVYjwSyRpJ7Z+fhpwIAtmCUdZIWZ/YP5R9WAsyuSgpLVDi9bjWoN2LXHNss/dk3urXtdQxGg==} engines: {node: '>=6'} + deep-eql@5.0.2: + resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==} + engines: {node: '>=6'} + deep-equal@2.2.3: resolution: {integrity: sha512-ZIwpnevOurS8bpT4192sqAowWM76JDKSHYzMLty3BZGSswgq6pBaH3DhCSW5xVAZICZyKdOBPjwww5wfgT/6PA==} engines: {node: '>= 0.4'} @@ -5018,6 +5292,9 @@ packages: resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==} engines: {node: '>=6.0.0'} + dom-accessibility-api@0.5.16: + resolution: {integrity: sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==} + dom-helpers@5.2.1: resolution: {integrity: sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==} @@ -5056,6 +5333,10 @@ packages: duck@0.1.12: resolution: {integrity: sha512-wkctla1O6VfP89gQ+J/yDesM0S7B7XLXjKGzXxMDVFg7uEn706niAtyYovKbyq1oT9YwDcly721/iUWoc8MVRg==} + dunder-proto@1.0.1: + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} + engines: {node: '>= 0.4'} + eastasianwidth@0.2.0: resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} @@ -5081,6 +5362,9 @@ packages: electron-to-chromium@1.4.829: resolution: {integrity: sha512-5qp1N2POAfW0u1qGAxXEtz6P7bO1m6gpZr5hdf5ve6lxpLM7MpiM4jIPz7xcrNlClQMafbyUDDWjlIQZ1Mw0Rw==} + electron-to-chromium@1.5.96: + resolution: {integrity: sha512-8AJUW6dh75Fm/ny8+kZKJzI1pgoE8bKLZlzDU2W1ENd+DXKJrx7I7l9hb8UWR4ojlnb5OlixMt00QWiYJoVw1w==} + elkjs@0.9.3: resolution: {integrity: sha512-f/ZeWvW/BCXbhGEf1Ujp29EASo/lk1FDnETgNKwJrsVvGZhUWCZyg3xLJjAsxfOmt8KjswHmI5EwCQcPMpOYhQ==} @@ -5140,6 +5424,10 @@ packages: resolution: {integrity: sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==} engines: {node: '>= 0.4'} + es-define-property@1.0.1: + resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} + engines: {node: '>= 0.4'} + es-errors@1.3.0: resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} engines: {node: '>= 0.4'} @@ -5154,12 +5442,19 @@ packages: es-module-lexer@1.5.4: resolution: {integrity: sha512-MVNK56NiMrOwitFB7cqDwq0CQutbw+0BvLshJSse0MUNU+y1FC3bUS/AQg7oUng+/wKrrki7JfmwtVHkVfPLlw==} + es-module-lexer@1.6.0: + resolution: {integrity: sha512-qqnD1yMU6tk/jnaMosogGySTZP8YtUgAffA9nMN+E/rjxcfRQ6IEk7IiozUjgxKoFHBGjTLnrHB/YC45r/59EQ==} + es-object-atoms@1.0.0: resolution: {integrity: sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==} engines: {node: '>= 0.4'} - es-set-tostringtag@2.0.3: - resolution: {integrity: sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ==} + es-object-atoms@1.1.1: + resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} + engines: {node: '>= 0.4'} + + es-set-tostringtag@2.1.0: + resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} engines: {node: '>= 0.4'} es-shim-unscopables@1.0.2: @@ -5183,6 +5478,10 @@ packages: resolution: {integrity: sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==} engines: {node: '>=6'} + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + escape-html@1.0.3: resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} @@ -5364,6 +5663,10 @@ packages: resolution: {integrity: sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==} engines: {node: '>=6'} + expect-type@1.1.0: + resolution: {integrity: sha512-bFi65yM+xZgk+u/KRIpekdSYkTB5W1pEf0Lt8Q8Msh7b+eQ7LXVtIB1Bkm4fvclDEL1b2CZkMhv2mOeF8tMdkA==} + engines: {node: '>=12.0.0'} + expect@29.7.0: resolution: {integrity: sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -5522,15 +5825,6 @@ packages: resolution: {integrity: sha512-QFaHbhv9WPUeLYBDe/PAuLKJ4Dd9OPvKs9xZBr3yLXnUrDNaVXKu2baDBXe3naPY30hgHYSsf2JW4jzas2mDEQ==} engines: {node: '>=10'} - follow-redirects@1.15.6: - resolution: {integrity: sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==} - engines: {node: '>=4.0'} - peerDependencies: - debug: '*' - peerDependenciesMeta: - debug: - optional: true - follow-redirects@1.15.9: resolution: {integrity: sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==} engines: {node: '>=4.0'} @@ -5561,6 +5855,10 @@ packages: resolution: {integrity: sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==} engines: {node: '>= 6'} + form-data@4.0.2: + resolution: {integrity: sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==} + engines: {node: '>= 6'} + format@0.2.2: resolution: {integrity: sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==} engines: {node: '>=0.4.x'} @@ -5652,6 +5950,10 @@ packages: resolution: {integrity: sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==} engines: {node: '>= 0.4'} + get-intrinsic@1.3.0: + resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} + engines: {node: '>= 0.4'} + get-nonce@1.0.1: resolution: {integrity: sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==} engines: {node: '>=6'} @@ -5660,6 +5962,10 @@ packages: resolution: {integrity: sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==} engines: {node: '>=8.0.0'} + get-proto@1.0.1: + resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} + engines: {node: '>= 0.4'} + get-stream@2.3.1: resolution: {integrity: sha512-AUGhbbemXxrZJRD5cDvKtQxLuYaIbNtDTK8YqupCI393Q2KSTreEsLUN3ZxAWFGiKTzL6nKuzfcIvieflUX9qA==} engines: {node: '>=0.10.0'} @@ -5731,9 +6037,16 @@ packages: resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} engines: {node: '>=10'} + globrex@0.1.2: + resolution: {integrity: sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==} + gopd@1.0.1: resolution: {integrity: sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==} + gopd@1.2.0: + resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} + engines: {node: '>= 0.4'} + graceful-fs@4.2.11: resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} @@ -5766,6 +6079,10 @@ packages: resolution: {integrity: sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==} engines: {node: '>= 0.4'} + has-symbols@1.1.0: + resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} + engines: {node: '>= 0.4'} + has-tostringtag@1.0.2: resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} engines: {node: '>= 0.4'} @@ -5823,6 +6140,10 @@ packages: hoist-non-react-statics@3.3.2: resolution: {integrity: sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==} + html-encoding-sniffer@4.0.0: + resolution: {integrity: sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==} + engines: {node: '>=18'} + html-entities@2.5.2: resolution: {integrity: sha512-K//PSRMQk4FZ78Kyau+mZurHn3FH0Vwr+H36eE0rPbeYkRRi9YxceYPhuN60UwWorxyKHhqoAJl2OFKa4BVtaA==} @@ -5857,6 +6178,10 @@ packages: resolution: {integrity: sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==} engines: {node: '>= 14'} + https-proxy-agent@7.0.6: + resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} + engines: {node: '>= 14'} + human-signals@2.1.0: resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} engines: {node: '>=10.17.0'} @@ -6127,6 +6452,9 @@ packages: resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==} engines: {node: '>=12'} + is-potential-custom-element-name@1.0.1: + resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==} + is-property@1.0.2: resolution: {integrity: sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==} @@ -6235,6 +6563,10 @@ packages: resolution: {integrity: sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==} engines: {node: '>=10'} + istanbul-lib-source-maps@5.0.6: + resolution: {integrity: sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==} + engines: {node: '>=10'} + istanbul-reports@3.1.7: resolution: {integrity: sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==} engines: {node: '>=8'} @@ -6425,8 +6757,22 @@ packages: resolution: {integrity: sha512-Jbygqaa20I+0ImPjmMbrsY3QrMkfwfI5G/VNlb6c9nDIyyOw8msfWHzTy04/sawa4rjn0t9WYy3nahWlSjB5zw==} engines: {node: '>=0.1.90'} +<<<<<<< HEAD + jsep@1.4.0: + resolution: {integrity: sha512-B7qPcEVE3NVkmSJbaYxvv4cHkVW7DQsZz13pUMrfS8z8Q/BuShN+gcTXrUlPiGqM2/t/EEaI030bpxMqY8gMlw==} +======= + jsdom@26.0.0: + resolution: {integrity: sha512-BZYDGVAIriBWTpIxYzrXjv3E/4u8+/pSG5bQdIYCbNCGOvsPkDQfTVLAIXAf9ETdCpduCVTkDe2NNZ8NIwUVzw==} + engines: {node: '>=18'} + peerDependencies: + canvas: ^3.0.0 + peerDependenciesMeta: + canvas: + optional: true + jsep@1.3.9: resolution: {integrity: sha512-i1rBX5N7VPl0eYb6+mHNp52sEuaS2Wi8CDYx1X5sn9naevL78+265XJqy1qENEk7mRKwS06NHpUqiBwR7qeodw==} +>>>>>>> 80a0b9296 (chore: vitest) engines: {node: '>= 10.16.0'} jsesc@0.5.0: @@ -6438,6 +6784,11 @@ packages: engines: {node: '>=4'} hasBin: true + jsesc@3.1.0: + resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} + engines: {node: '>=6'} + hasBin: true + json-buffer@3.0.1: resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} @@ -6479,8 +6830,8 @@ packages: jsonfile@6.1.0: resolution: {integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==} - jsonpath-plus@10.1.0: - resolution: {integrity: sha512-gHfV1IYqH8uJHYVTs8BJX1XKy2/rR93+f8QQi0xhx95aCiXn1ettYAd5T+7FU6wfqyDoX/wy0pm/fL3jOKJ9Lg==} + jsonpath-plus@10.3.0: + resolution: {integrity: sha512-8TNmfeTCk2Le33A3vRRwtuworG/L5RrgMvdjhKZxvyShO+mBu2fP50OWUjRLNtvw344DdDarFh9buFAZs5ujeA==} engines: {node: '>=18.0.0'} hasBin: true @@ -6507,8 +6858,8 @@ packages: jws@4.0.0: resolution: {integrity: sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==} - kareem@2.5.1: - resolution: {integrity: sha512-7jFxRVm+jD+rkq3kY0iZDJfsO2/t4BBPeEb2qKn2lR/9KhuksYk5hxzfRYWMPV8P/x2d0kHD306YyWLzjjH+uA==} + kareem@2.6.3: + resolution: {integrity: sha512-C3iHfuGUXK2u8/ipq9LfjFfXFxAZMQJJq7vLS45r3D9Y2xQ/m4S8zaR4zMLFWh9AsNPXmcFfUDhTEO8UIC/V6Q==} engines: {node: '>=12.0.0'} katex@0.16.11: @@ -6556,8 +6907,8 @@ packages: lexical@0.14.5: resolution: {integrity: sha512-ouV7Gyr9+3WT3WTrCgRAD3iZnlJWfs2/kBl2x3J2Q3X9uCWJn/zn21fQ8G1EUHlu0dvXPBmdk9hXb/FjTClt6Q==} - lib0@0.2.98: - resolution: {integrity: sha512-XteTiNO0qEXqqweWx+b21p/fBnNHUA1NwAtJNJek1oPrewEZs2uiT4gWivHKr9GqCjDPAhchz0UQO8NwU3bBNA==} + lib0@0.2.99: + resolution: {integrity: sha512-vwztYuUf1uf/1zQxfzRfO5yzfNKhTtgOByCruuiQQxWQXnPb8Itaube5ylofcV0oM0aKal9Mv+S1s1Ky0UYP1w==} engines: {node: '>=16'} hasBin: true @@ -6682,6 +7033,9 @@ packages: loupe@2.3.7: resolution: {integrity: sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==} + loupe@3.1.3: + resolution: {integrity: sha512-kkIp7XSkP78ZxJEsSxW3712C6teJVoeHHwgo9zJ380de7IYyJ2ISlxojcH2pC5OFLewESmnRi/+XCDIEEVyoug==} + lowlight@1.20.0: resolution: {integrity: sha512-8Ktj+prEb1RoCPkEOrPMYUN/nCggB7qAWe3a7OpMjWQkh3l2RD5wKRQ+o8Q8YuI9RG/xs95waaI/E6ym/7NsTw==} @@ -6707,13 +7061,23 @@ packages: resolution: {integrity: sha512-zobTr7akeGHnv7eBOXcRgMeCP6+uyYsczwmeRCauvpvaAltgNyTbLH/+VaEAPUeWBT+1GuNmz4wC/6jtQzbbVA==} engines: {node: '>=12'} + lz-string@1.5.0: + resolution: {integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==} + hasBin: true + magic-string@0.30.10: resolution: {integrity: sha512-iIRwTIf0QKV3UAnYK4PU8uiEc4SRh5jX0mwpIwETPpHdhVM4f53RSwS/vXvN1JhGX+Cs7B8qIq3d6AH49O5fAQ==} + magic-string@0.30.17: + resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==} + magic-string@0.30.8: resolution: {integrity: sha512-ISQTe55T2ao7XtlAStud6qwYPZjE4GK1S/BeVPus4jrq6JuOnQ00YKQC581RWhR122W7msZV263KzVeLoqidyQ==} engines: {node: '>=12'} + magicast@0.3.5: + resolution: {integrity: sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==} + make-dir@1.3.0: resolution: {integrity: sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==} engines: {node: '>=4'} @@ -6747,6 +7111,10 @@ packages: markdown-table@3.0.3: resolution: {integrity: sha512-Z1NL3Tb1M9wH4XESsCDEksWoKTdlUafKc4pt0GRwjUyXaCFZ+dc3g2erqB6zm3szA2IUSi7VnPI+o/9jnxh9hw==} + math-intrinsics@1.1.0: + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} + engines: {node: '>= 0.4'} + mdast-util-find-and-replace@3.0.1: resolution: {integrity: sha512-SG21kZHGC3XRTSUhtofZkBzZTJNM5ecCi0SK2IMKmSXR8vO3peL+kb1O0z7Zl83jKtutG4k5Wv/W7V3/YHvzPA==} @@ -7126,6 +7494,10 @@ packages: resolution: {integrity: sha512-eib56lLOWiJneikoLJE0R0mfnVcKD6wYiEuUNc5bxdhKQZRyPGy6SDp8tVCoJNJdxAgCjlUIurhaq1NtUIHe1A==} engines: {node: '>=16.20.1'} + mongodb-memory-server-core@10.1.3: + resolution: {integrity: sha512-ayBQHeV74wRHhgcAKpxHYI4th9Ufidy/m3XhJnLFRufKsOyDsyHYU3Zxv5Fm4hxsWE6wVd0GAVcQ7t7XNkivOg==} + engines: {node: '>=16.20.1'} + mongodb-memory-server-core@9.2.0: resolution: {integrity: sha512-9SWZEy+dGj5Fvm5RY/mtqHZKS64o4heDwReD4SsfR7+uNgtYo+JN41kPCcJeIH3aJf04j25i5Dia2s52KmsMPA==} engines: {node: '>=14.20.1'} @@ -7134,6 +7506,10 @@ packages: resolution: {integrity: sha512-muyyvITwY3MIfzOedskcO5LJeZs+JBt/T5q5beeXWuXEsMED8o1AGMXzbv2Y2mrmXQscbYnJ1aNXIOfV54HPPQ==} engines: {node: '>=16.20.1'} + mongodb-memory-server@10.1.3: + resolution: {integrity: sha512-QCUjsIIXSYv/EgkpDAjfhlqRKo6N+qR6DD43q4lyrCVn24xQmvlArdWHW/Um5RS4LkC9YWC3XveSncJqht2Hbg==} + engines: {node: '>=16.20.1'} + mongodb-memory-server@9.2.0: resolution: {integrity: sha512-w/usKdYtby5EALERxmA0+et+D0brP0InH3a26shNDgGefXA61hgl6U0P3IfwqZlEGRZdkbZig3n57AHZgDiwvg==} engines: {node: '>=14.20.1'} @@ -7159,6 +7535,33 @@ packages: snappy: optional: true + mongodb@6.13.1: + resolution: {integrity: sha512-gdq40tX8StmhP6akMp1pPoEVv+9jTYFSrga/g23JxajPAQhH39ysZrHGzQCSd9PEOnuEQEdjIWqxO7ZSwC0w7Q==} + engines: {node: '>=16.20.1'} + peerDependencies: + '@aws-sdk/credential-providers': ^3.632.0 + '@mongodb-js/zstd': ^1.1.0 || ^2.0.0 + gcp-metadata: ^5.2.0 + kerberos: ^2.0.1 + mongodb-client-encryption: '>=6.0.0 <7' + snappy: ^7.2.2 + socks: ^2.7.1 + peerDependenciesMeta: + '@aws-sdk/credential-providers': + optional: true + '@mongodb-js/zstd': + optional: true + gcp-metadata: + optional: true + kerberos: + optional: true + mongodb-client-encryption: + optional: true + snappy: + optional: true + socks: + optional: true + mongodb@6.9.0: resolution: {integrity: sha512-UMopBVx1LmEUbW/QE0Hw18u583PEDVQmUmVzzBRH0o/xtE9DBRA5ZYLOjpLIa03i8FXjzvQECJcqoMvCXftTUA==} engines: {node: '>=16.20.1'} @@ -7186,9 +7589,9 @@ packages: socks: optional: true - mongoose@7.8.2: - resolution: {integrity: sha512-/KDcZL84gg8hnmOHRRPK49WtxH3Xsph38c7YqvYPdxEB2OsDAXvwAknGxyEC0F2P3RJCqFOp+523iFCa0p3dfw==} - engines: {node: '>=14.20.1'} + mongoose@8.10.2: + resolution: {integrity: sha512-DvqfK1s/JLwP39ogXULC8ygNDdmDber5ZbxZzELYtkzl9VGJ3K5T2MCLdpTs9I9J6DnkDyIHJwt7IOyMxh/Adw==} + engines: {node: '>=16.20.1'} mpath@0.9.0: resolution: {integrity: sha512-ikJRQTk8hw5DEoFVxHG1Gn9T/xcjtdnOKIU1JTmGjZZlg9LST2mBLmcX3/ICIbgJydT2GOc15RnNy5mHmzfSew==} @@ -7238,8 +7641,8 @@ packages: nan@2.22.0: resolution: {integrity: sha512-nbajikzWTMwsW+eSsNm3QwlOs7het9gGJU5dDZzRTQGk03vyBOauxgI4VakDzE0PtsGTmXPsXTbbjVhRwR5mpw==} - nanoid@3.3.7: - resolution: {integrity: sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==} + nanoid@3.3.9: + resolution: {integrity: sha512-SppoicMGpZvbF1l3z4x7No3OlIjP7QJvC9XR7AhZr1kL133KHnKPztkKDc+Ir4aJ/1VhTySrtKhrsycmrMQfvg==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true @@ -7282,8 +7685,8 @@ packages: react: '>= 17.0.2' react-i18next: '>= 13.5.0' - next@14.2.5: - resolution: {integrity: sha512-0f8aRfBVL+mpzfBjYfQuLWh2WyAwtJXCRfkPF4UJ5qd2YwrHczsrSzXU4tRMV0OAxR8ZJZWPFn6uhSC56UTsLA==} + next@14.2.21: + resolution: {integrity: sha512-rZmLwucLHr3/zfDMYbJXbw0ZeoBpirxkXuvsJbk7UPorvPYZhP7vq7aHbKnU7dQNCYIimRrbB2pp3xmf+wsYUg==} engines: {node: '>=18.17.0'} hasBin: true peerDependencies: @@ -7349,6 +7752,9 @@ packages: node-releases@2.0.17: resolution: {integrity: sha512-Ww6ZlOiEQfPfXM45v17oabk77Z7mg5bOt7AjDyzy7RjK9OrLrLC8dyZQoAPEOtFX9SaNf1Tdvr5gRJWdTJj7GA==} + node-releases@2.0.19: + resolution: {integrity: sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==} + node-xlsx@0.24.0: resolution: {integrity: sha512-1olwK48XK9nXZsyH/FCltvGrQYvXXZuxVitxXXv2GIuRm51aBi1+5KwR4rWM4KeO61sFU+00913WLZTD+AcXEg==} engines: {node: '>=10.0.0'} @@ -7389,6 +7795,9 @@ packages: nth-check@2.1.1: resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} + nwsapi@2.2.16: + resolution: {integrity: sha512-F1I/bimDpj3ncaNDhfyMWuFqmQDBwDB0Fogc2qpL3BWvkQteFD/8BzWuIRl83rq0DXfm8SGt/HFhLXZyljTXcQ==} + object-assign@4.1.1: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} engines: {node: '>=0.10.0'} @@ -7544,6 +7953,9 @@ packages: parse5@7.2.0: resolution: {integrity: sha512-ZkDsAOcxsUMZ4Lz5fVciOehNcJ+Gb8gTzcA4yl3wnc273BAybYWrQ+Ks/OjCjSEpjvQkDSeZbybK9qj2VHHdGA==} + parse5@7.2.1: + resolution: {integrity: sha512-BuBYQYlv1ckiPdQi/ohiivi9Sagc9JG+Ozs0r7b/0iK3sKmrb0b9FdWdBbOdx6hBCM/F9Ir82ofnBhtZOjCRPQ==} + parseurl@1.3.3: resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} engines: {node: '>= 0.8'} @@ -7591,9 +8003,16 @@ packages: pathe@1.1.2: resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==} + pathe@2.0.2: + resolution: {integrity: sha512-15Ztpk+nov8DR524R4BF7uEuzESgzUEAV4Ah7CUMNGXdE5ELuvxElxGXndBl32vMSsWa1jpNf22Z+Er3sKwq+w==} + pathval@1.1.1: resolution: {integrity: sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==} + pathval@2.0.0: + resolution: {integrity: sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==} + engines: {node: '>= 14.16'} + pdfjs-dist@4.4.168: resolution: {integrity: sha512-MbkAjpwka/dMHaCfQ75RY1FXX3IewBVu6NGZOcxerRFlaBiIkZmUoR0jotX5VUzYZEXAGzSFtknWs5xRKliXPA==} engines: {node: '>=18'} @@ -7719,8 +8138,8 @@ packages: resolution: {integrity: sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==} engines: {node: ^10 || ^12 || >=14} - postcss@8.4.39: - resolution: {integrity: sha512-0vzE+lAiG7hZl1/9I8yzKLx3aR9Xbof3fBHKunvMfOCYAtMhrsnccJY2iTURb9EZd5+pLuiNV9/c/GZJOHsgIw==} + postcss@8.5.3: + resolution: {integrity: sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==} engines: {node: ^10 || ^12 || >=14} postgres-array@2.0.0: @@ -7777,6 +8196,10 @@ packages: engines: {node: '>=14'} hasBin: true + pretty-format@27.5.1: + resolution: {integrity: sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==} + engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + pretty-format@29.7.0: resolution: {integrity: sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -7974,6 +8397,10 @@ packages: react-native: optional: true + react-refresh@0.14.2: + resolution: {integrity: sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==} + engines: {node: '>=0.10.0'} + react-remove-scroll-bar@2.3.6: resolution: {integrity: sha512-DtSYaao4mBmX+HDo5YWYdBWQwYIQQshUV/dVxFxK+KM26Wjwp1gZ6rv6OC3oujI6Bfu6Xyg3TwK533AQutsn/g==} engines: {node: '>=10'} @@ -8218,6 +8645,9 @@ packages: engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true + rrweb-cssom@0.8.0: + resolution: {integrity: sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==} + run-async@2.4.1: resolution: {integrity: sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==} engines: {node: '>=0.12.0'} @@ -8275,6 +8705,10 @@ packages: sax@1.4.1: resolution: {integrity: sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==} + saxes@6.0.0: + resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==} + engines: {node: '>=v12.22.7'} + scheduler@0.23.2: resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==} @@ -8348,8 +8782,8 @@ packages: resolution: {integrity: sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==} engines: {node: '>= 0.4'} - sift@16.0.1: - resolution: {integrity: sha512-Wv6BjQ5zbhW7VFefWusVP33T/EM0vYikCaQ2qR8yULbsilAT8/wQaXvuQ3ptGLpoKx+lihJE3y2UTgKDyyNHZQ==} + sift@17.1.3: + resolution: {integrity: sha512-Rtlj66/b0ICeFzYTuNvX/EF1igRbbnGSvEyT79McoZa/DeGhMyC5pWKOEsZKnpkqtSeovd5FL/bjHWC3CIIvCQ==} siginfo@2.0.0: resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} @@ -8399,10 +8833,6 @@ packages: sonic-boom@4.0.1: resolution: {integrity: sha512-hTSD/6JMLyT4r9zeof6UtuBDpjJ9sO08/nmS5djaA9eozT9oOlNdpXSnzcgj4FTqpk3nkLrs61l4gip9r1HCrQ==} - source-map-js@1.2.0: - resolution: {integrity: sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==} - engines: {node: '>=0.10.0'} - source-map-js@1.2.1: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} @@ -8479,6 +8909,9 @@ packages: std-env@3.7.0: resolution: {integrity: sha512-JPbdCEQLj1w5GilpiHAx3qJvFndqybBysA3qUOnznweH4QbNYUsW/ea8QzSrnh0vNsezMMw5bcVool8lM0gwzg==} + std-env@3.8.0: + resolution: {integrity: sha512-Bc3YwwCB+OzldMxOXJIIvC6cPRWr/LxOp48CdQTOkPyk/t4JWWJbrilwBd7RJzKV8QW7tJkcgAmeuLLJugl5/w==} + stop-iteration-iterator@1.0.0: resolution: {integrity: sha512-iCGQj+0l0HOdZ2AEeBADlsRC+vsnDsZsbdSiH1yNSjcfKM7fdpCMfqAL/dwF5BLiw/XhRft/Wax6zQbhq2BcjQ==} engines: {node: '>= 0.4'} @@ -8644,6 +9077,9 @@ packages: resolution: {integrity: sha512-b19dMThMV4HVFynSAM1++gBHAbk2Tc/osgLIBZMKsyqh34jb2e8Os7T6ZW/Bt3pJFdBTd2JwAnAAEQV7rSNvcQ==} engines: {node: '>=0.10'} + symbol-tree@3.2.4: + resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} + tapable@2.2.1: resolution: {integrity: sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==} engines: {node: '>=6'} @@ -8699,6 +9135,10 @@ packages: resolution: {integrity: sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==} engines: {node: '>=8'} + test-exclude@7.0.1: + resolution: {integrity: sha512-pFYqmTw68LXVjeWJMST4+borgQP2AyMNbg1BpZh9LbyhUeNkeaPF9gzfPGUAnSMV3qPYdWUwDIjjCLiSDOl7vg==} + engines: {node: '>=18'} + text-decoder@1.1.1: resolution: {integrity: sha512-8zll7REEv4GDD3x4/0pW+ppIxSNs7H1J10IKFZsuOMscumCdM2a+toDGLPA3T+1+fLBql4zbt5z83GEQGGV5VA==} @@ -8726,14 +9166,39 @@ packages: tinybench@2.8.0: resolution: {integrity: sha512-1/eK7zUnIklz4JUUlL+658n58XO2hHLQfSk1Zf2LKieUjxidN16eKFEoDEfjHc3ohofSSqK3X5yO6VGb6iW8Lw==} + tinybench@2.9.0: + resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} + + tinyexec@0.3.2: + resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==} + tinypool@0.8.4: resolution: {integrity: sha512-i11VH5gS6IFeLY3gMBQ00/MmLncVP7JLXOw1vlgkytLmJK7QnEr7NXf0LBdxfmNPAeyetukOk0bOYrJrFGjYJQ==} engines: {node: '>=14.0.0'} + tinypool@1.0.2: + resolution: {integrity: sha512-al6n+QEANGFOMf/dmUMsuS5/r9B06uwlyNjZZql/zv8J7ybHCgoihBNORZCY2mzUuAnomQa2JdhyHKzZxPCrFA==} + engines: {node: ^18.0.0 || >=20.0.0} + + tinyrainbow@2.0.0: + resolution: {integrity: sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==} + engines: {node: '>=14.0.0'} + tinyspy@2.2.1: resolution: {integrity: sha512-KYad6Vy5VDWV4GH3fjpseMQ/XU2BhIYP7Vzd0LG44qRWm/Yt2WCOTicFdvmgo6gWaqooMQCawTtILVQJupKu7A==} engines: {node: '>=14.0.0'} + tinyspy@3.0.2: + resolution: {integrity: sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==} + engines: {node: '>=14.0.0'} + + tldts-core@6.1.76: + resolution: {integrity: sha512-uzhJ02RaMzgQR3yPoeE65DrcHI6LoM4saUqXOt/b5hmb3+mc4YWpdSeAQqVqRUlQ14q8ZuLRWyBR1ictK1dzzg==} + + tldts@6.1.76: + resolution: {integrity: sha512-6U2ti64/nppsDxQs9hw8ephA3nO6nSQvVVfxwRw8wLQPFtLI1cFI1a1eP22g+LUP+1TA2pKKjUTwWB+K2coqmQ==} + hasBin: true + tmp@0.0.33: resolution: {integrity: sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==} engines: {node: '>=0.6.0'} @@ -8767,6 +9232,10 @@ packages: resolution: {integrity: sha512-lbDrTLVsHhOMljPscd0yitpozq7Ga2M5Cvez5AjGg8GASBjtt6iERCAJ93yommPmz62fb45oFIXHEZ3u9bfJEA==} engines: {node: '>=14.16'} + tough-cookie@5.1.1: + resolution: {integrity: sha512-Ek7HndSVkp10hmHP9V4qZO1u+pn1RU5sI0Fw+jCU3lyvuMZcgqsNgc6CmJJZyByK4Vm/qotGRJlfgAX8q+4JiA==} + engines: {node: '>=16'} + tr46@0.0.3: resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} @@ -8778,6 +9247,10 @@ packages: resolution: {integrity: sha512-2lv/66T7e5yNyhAAC4NaKe5nVavzuGJQVVtRYLyQ2OI8tsJ61PMLlelehb0wi2Hx6+hT/OJUWZcw8MjlSRnxvw==} engines: {node: '>=14'} + tr46@5.0.0: + resolution: {integrity: sha512-tk2G5R2KRwBd+ZN0zaEXpmzdKyOYksXwywulIX95MBODjSzMIuQnQ3m8JxgbhnL1LeVo7lqQKsYa1O3Htl7K5g==} + engines: {node: '>=18'} + tree-kill@1.2.2: resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} hasBin: true @@ -8857,6 +9330,16 @@ packages: '@swc/wasm': optional: true + tsconfck@3.1.5: + resolution: {integrity: sha512-CLDfGgUp7XPswWnezWwsCRxNmgQjhYq3VXHM0/XIRxhVrKw0M1if9agzryh1QS3nxjCROvV+xWxoJO1YctzzWg==} + engines: {node: ^18 || >=20} + hasBin: true + peerDependencies: + typescript: ^5.0.0 + peerDependenciesMeta: + typescript: + optional: true + tsconfig-paths-webpack-plugin@4.1.0: resolution: {integrity: sha512-xWFISjviPydmtmgeUAuXp4N1fky+VCtfhOkDUFIv5ea7p4wuTomI4QTrXvFBX2S4jZsmyTSrStQl+E+4w+RzxA==} engines: {node: '>=10.13.0'} @@ -8883,6 +9366,9 @@ packages: tslib@2.8.0: resolution: {integrity: sha512-jWVzBLplnCmoaTr13V9dYbiQ99wvZRd0vNWaDRg+aVYRcjDF3nDksxFDE/+fkXnKhpnUUkmx5pK/v8mCtLVqZA==} + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + tunnel-agent@0.6.0: resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==} @@ -9058,6 +9544,12 @@ packages: peerDependencies: browserslist: '>= 4.21.0' + update-browserslist-db@1.1.2: + resolution: {integrity: sha512-PPypAm5qvlD7XMZC3BujecnaOxwhrtoFR+Dqkk5Aa/6DssiH0ibKoketaj9w8LP7Bont1rYeoV5plxD7RTEPRg==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} @@ -9189,9 +9681,22 @@ packages: engines: {node: ^18.0.0 || >=20.0.0} hasBin: true - vite@5.3.4: - resolution: {integrity: sha512-Cw+7zL3ZG9/NZBB8C+8QbQZmR54GwqIz+WMI4b3JgdYJvX+ny9AjJXqkGQlDXSXRP9rP0B4tbciRMOVEKulVOA==} - engines: {node: ^18.0.0 || >=20.0.0} + vite-node@3.0.5: + resolution: {integrity: sha512-02JEJl7SbtwSDJdYS537nU6l+ktdvcREfLksk/NDAqtdKWGqHl+joXzEubHROmS3E6pip+Xgu2tFezMu75jH7A==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + hasBin: true + + vite-tsconfig-paths@5.1.4: + resolution: {integrity: sha512-cYj0LRuLV2c2sMqhqhGpaO3LretdtMn/BVX4cPLanIZuwwrkVl+lK84E/miEXkCHWXuq65rhNN4rXsBcOB3S4w==} + peerDependencies: + vite: '*' + peerDependenciesMeta: + vite: + optional: true + + vite@5.3.4: + resolution: {integrity: sha512-Cw+7zL3ZG9/NZBB8C+8QbQZmR54GwqIz+WMI4b3JgdYJvX+ny9AjJXqkGQlDXSXRP9rP0B4tbciRMOVEKulVOA==} + engines: {node: ^18.0.0 || >=20.0.0} hasBin: true peerDependencies: '@types/node': ^18.0.0 || >=20.0.0 @@ -9217,6 +9722,9 @@ packages: terser: optional: true + vitest-mongodb@1.0.1: + resolution: {integrity: sha512-a9Mc2F35h8qxI1uOgsrCUH28TglClAd8gdXkn7CBqmC6bLr6D2Ibyxp0Xz6/AU0ukAOfuf/6oqUS+ZN0VlxVyQ==} + vitest@1.6.0: resolution: {integrity: sha512-H5r/dN06swuFnzNFhq/dnz37bPXnq8xB2xB5JOVk8K09rUtoeNN+LHWkoQ0A/i3hvbUKKcCei9KpbxqHMLhLLA==} engines: {node: ^18.0.0 || >=20.0.0} @@ -9242,6 +9750,34 @@ packages: jsdom: optional: true + vitest@3.0.5: + resolution: {integrity: sha512-4dof+HvqONw9bvsYxtkfUp2uHsTN9bV2CZIi1pWgoFpL1Lld8LA1ka9q/ONSsoScAKG7NVGf2stJTI7XRkXb2Q==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@types/debug': ^4.1.12 + '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 + '@vitest/browser': 3.0.5 + '@vitest/ui': 3.0.5 + happy-dom: '*' + jsdom: '*' + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@types/debug': + optional: true + '@types/node': + optional: true + '@vitest/browser': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + void-elements@3.1.0: resolution: {integrity: sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==} engines: {node: '>=0.10.0'} @@ -9254,6 +9790,10 @@ packages: typescript: optional: true + w3c-xmlserializer@5.0.0: + resolution: {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==} + engines: {node: '>=18'} + walker@1.0.8: resolution: {integrity: sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==} @@ -9299,6 +9839,14 @@ packages: webpack-cli: optional: true + whatwg-encoding@3.1.1: + resolution: {integrity: sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==} + engines: {node: '>=18'} + + whatwg-mimetype@4.0.0: + resolution: {integrity: sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==} + engines: {node: '>=18'} + whatwg-url@11.0.0: resolution: {integrity: sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==} engines: {node: '>=12'} @@ -9307,6 +9855,10 @@ packages: resolution: {integrity: sha512-9WWbymnqj57+XEuqADHrCJ2eSXzn8WXIW/YSGaZtb2WKAInQ6CHfaUUcTyyver0p8BDg5StLQq8h1vtZuwmOig==} engines: {node: '>=16'} + whatwg-url@14.1.0: + resolution: {integrity: sha512-jlf/foYIKywAt3x/XWKZ/3rz8OSJPiWktjmk891alJUEjiVxKX9LEO92qH3hv4aJ0mN3MWPvGMCy8jQi95xK4w==} + engines: {node: '>=18'} + whatwg-url@5.0.0: resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} @@ -9381,16 +9933,35 @@ packages: resolution: {integrity: sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==} engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + ws@8.18.0: + resolution: {integrity: sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + xlsx@https://cdn.sheetjs.com/xlsx-0.20.2/xlsx-0.20.2.tgz: resolution: {tarball: https://cdn.sheetjs.com/xlsx-0.20.2/xlsx-0.20.2.tgz} version: 0.20.2 engines: {node: '>=0.8'} hasBin: true + xml-name-validator@5.0.0: + resolution: {integrity: sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==} + engines: {node: '>=18'} + xmlbuilder@10.1.1: resolution: {integrity: sha512-OyzrcFLL/nb6fMGHbiRDuPup9ljBycsdCypwuyg5AAHvyWzGfChJpCXMG88AGTIMFhGZ9RccFN1e6lhg3hkwKg==} engines: {node: '>=4.0'} + xmlchars@2.2.0: + resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==} + xtend@4.0.2: resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} engines: {node: '>=0.4'} @@ -9543,15 +10114,23 @@ snapshots: call-me-maybe: 1.0.2 openapi-types: 12.1.3 + '@asamuzakjp/css-color@2.8.3': + dependencies: + '@csstools/css-calc': 2.1.1(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3) + '@csstools/css-color-parser': 3.0.7(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3) + '@csstools/css-parser-algorithms': 3.0.4(@csstools/css-tokenizer@3.0.3) + '@csstools/css-tokenizer': 3.0.3 + lru-cache: 10.4.3 + '@azure/abort-controller@2.1.2': dependencies: - tslib: 2.8.0 + tslib: 2.8.1 '@azure/core-auth@1.9.0': dependencies: '@azure/abort-controller': 2.1.2 '@azure/core-util': 1.11.0 - tslib: 2.8.0 + tslib: 2.8.1 '@azure/core-client@1.9.2': dependencies: @@ -9561,7 +10140,7 @@ snapshots: '@azure/core-tracing': 1.2.0 '@azure/core-util': 1.11.0 '@azure/logger': 1.1.4 - tslib: 2.8.0 + tslib: 2.8.1 transitivePeerDependencies: - supports-color @@ -9578,11 +10157,11 @@ snapshots: '@azure/abort-controller': 2.1.2 '@azure/core-util': 1.11.0 '@azure/logger': 1.1.4 - tslib: 2.8.0 + tslib: 2.8.1 '@azure/core-paging@1.6.2': dependencies: - tslib: 2.8.0 + tslib: 2.8.1 '@azure/core-rest-pipeline@1.18.2': dependencies: @@ -9593,18 +10172,18 @@ snapshots: '@azure/logger': 1.1.4 http-proxy-agent: 7.0.2 https-proxy-agent: 7.0.5 - tslib: 2.8.0 + tslib: 2.8.1 transitivePeerDependencies: - supports-color '@azure/core-tracing@1.2.0': dependencies: - tslib: 2.8.0 + tslib: 2.8.1 '@azure/core-util@1.11.0': dependencies: '@azure/abort-controller': 2.1.2 - tslib: 2.8.0 + tslib: 2.8.1 '@azure/identity@4.6.0': dependencies: @@ -9621,7 +10200,7 @@ snapshots: jws: 4.0.0 open: 8.4.2 stoppable: 1.1.0 - tslib: 2.8.0 + tslib: 2.8.1 transitivePeerDependencies: - supports-color @@ -9634,7 +10213,7 @@ snapshots: '@azure/core-tracing': 1.2.0 '@azure/core-util': 1.11.0 '@azure/logger': 1.1.4 - tslib: 2.8.0 + tslib: 2.8.1 transitivePeerDependencies: - supports-color @@ -9651,13 +10230,13 @@ snapshots: '@azure/core-util': 1.11.0 '@azure/keyvault-common': 2.0.0 '@azure/logger': 1.1.4 - tslib: 2.8.0 + tslib: 2.8.1 transitivePeerDependencies: - supports-color '@azure/logger@1.1.4': dependencies: - tslib: 2.8.0 + tslib: 2.8.1 '@azure/msal-browser@4.0.2': dependencies: @@ -9676,10 +10255,21 @@ snapshots: '@babel/code-frame@7.24.7': dependencies: '@babel/highlight': 7.24.7 - picocolors: 1.0.1 + picocolors: 1.1.1 +<<<<<<< HEAD +======= + + '@babel/code-frame@7.26.2': + dependencies: + '@babel/helper-validator-identifier': 7.25.9 + js-tokens: 4.0.0 + picocolors: 1.1.1 +>>>>>>> 80a0b9296 (chore: vitest) '@babel/compat-data@7.24.9': {} + '@babel/compat-data@7.26.8': {} + '@babel/core@7.24.9': dependencies: '@ampproject/remapping': 2.3.0 @@ -9693,6 +10283,27 @@ snapshots: '@babel/traverse': 7.25.6 '@babel/types': 7.25.6 convert-source-map: 2.0.0 + debug: 4.4.0 + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/core@7.26.8': + dependencies: + '@ampproject/remapping': 2.3.0 + '@babel/code-frame': 7.26.2 + '@babel/generator': 7.26.8 + '@babel/helper-compilation-targets': 7.26.5 + '@babel/helper-module-transforms': 7.26.0(@babel/core@7.26.8) + '@babel/helpers': 7.26.7 + '@babel/parser': 7.26.8 + '@babel/template': 7.26.8 + '@babel/traverse': 7.26.8 + '@babel/types': 7.26.8 + '@types/gensync': 1.0.4 + convert-source-map: 2.0.0 debug: 4.3.7 gensync: 1.0.0-beta.2 json5: 2.2.3 @@ -9714,6 +10325,14 @@ snapshots: '@jridgewell/trace-mapping': 0.3.25 jsesc: 2.5.2 + '@babel/generator@7.26.8': + dependencies: + '@babel/parser': 7.26.8 + '@babel/types': 7.26.8 + '@jridgewell/gen-mapping': 0.3.5 + '@jridgewell/trace-mapping': 0.3.25 + jsesc: 3.1.0 + '@babel/helper-annotate-as-pure@7.24.7': dependencies: '@babel/types': 7.25.6 @@ -9733,6 +10352,14 @@ snapshots: lru-cache: 5.1.1 semver: 6.3.1 + '@babel/helper-compilation-targets@7.26.5': + dependencies: + '@babel/compat-data': 7.26.8 + '@babel/helper-validator-option': 7.25.9 + browserslist: 4.24.4 + lru-cache: 5.1.1 + semver: 6.3.1 + '@babel/helper-create-class-features-plugin@7.24.8(@babel/core@7.24.9)': dependencies: '@babel/core': 7.24.9 @@ -9760,7 +10387,7 @@ snapshots: '@babel/core': 7.24.9 '@babel/helper-compilation-targets': 7.24.8 '@babel/helper-plugin-utils': 7.24.8 - debug: 4.3.7 + debug: 4.4.0 lodash.debounce: 4.0.8 resolve: 1.22.8 transitivePeerDependencies: @@ -9793,6 +10420,13 @@ snapshots: transitivePeerDependencies: - supports-color + '@babel/helper-module-imports@7.25.9': + dependencies: + '@babel/traverse': 7.26.8 + '@babel/types': 7.26.8 + transitivePeerDependencies: + - supports-color + '@babel/helper-module-transforms@7.24.9(@babel/core@7.24.9)': dependencies: '@babel/core': 7.24.9 @@ -9804,12 +10438,23 @@ snapshots: transitivePeerDependencies: - supports-color + '@babel/helper-module-transforms@7.26.0(@babel/core@7.26.8)': + dependencies: + '@babel/core': 7.26.8 + '@babel/helper-module-imports': 7.25.9 + '@babel/helper-validator-identifier': 7.25.9 + '@babel/traverse': 7.26.8 + transitivePeerDependencies: + - supports-color + '@babel/helper-optimise-call-expression@7.24.7': dependencies: '@babel/types': 7.24.9 '@babel/helper-plugin-utils@7.24.8': {} + '@babel/helper-plugin-utils@7.26.5': {} + '@babel/helper-remap-async-to-generator@7.24.7(@babel/core@7.24.9)': dependencies: '@babel/core': 7.24.9 @@ -9848,10 +10493,16 @@ snapshots: '@babel/helper-string-parser@7.24.8': {} + '@babel/helper-string-parser@7.25.9': {} + '@babel/helper-validator-identifier@7.24.7': {} + '@babel/helper-validator-identifier@7.25.9': {} + '@babel/helper-validator-option@7.24.8': {} + '@babel/helper-validator-option@7.25.9': {} + '@babel/helper-wrap-function@7.24.7': dependencies: '@babel/helper-function-name': 7.24.7 @@ -9863,15 +10514,20 @@ snapshots: '@babel/helpers@7.24.8': dependencies: - '@babel/template': 7.24.7 + '@babel/template': 7.25.0 '@babel/types': 7.25.6 + '@babel/helpers@7.26.7': + dependencies: + '@babel/template': 7.26.8 + '@babel/types': 7.26.8 + '@babel/highlight@7.24.7': dependencies: '@babel/helper-validator-identifier': 7.24.7 chalk: 2.4.2 js-tokens: 4.0.0 - picocolors: 1.0.1 + picocolors: 1.1.1 '@babel/parser@7.24.8': dependencies: @@ -9881,6 +10537,10 @@ snapshots: dependencies: '@babel/types': 7.25.6 + '@babel/parser@7.26.8': + dependencies: + '@babel/types': 7.26.8 + '@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.24.7(@babel/core@7.24.9)': dependencies: '@babel/core': 7.24.9 @@ -10295,6 +10955,16 @@ snapshots: transitivePeerDependencies: - supports-color + '@babel/plugin-transform-react-jsx-self@7.25.9(@babel/core@7.26.8)': + dependencies: + '@babel/core': 7.26.8 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-transform-react-jsx-source@7.25.9(@babel/core@7.26.8)': + dependencies: + '@babel/core': 7.26.8 + '@babel/helper-plugin-utils': 7.26.5 + '@babel/plugin-transform-react-jsx@7.24.7(@babel/core@7.24.9)': dependencies: '@babel/core': 7.24.9 @@ -10523,6 +11193,12 @@ snapshots: '@babel/parser': 7.25.6 '@babel/types': 7.25.6 + '@babel/template@7.26.8': + dependencies: + '@babel/code-frame': 7.26.2 + '@babel/parser': 7.26.8 + '@babel/types': 7.26.8 + '@babel/traverse@7.24.8': dependencies: '@babel/code-frame': 7.24.7 @@ -10533,7 +11209,7 @@ snapshots: '@babel/helper-split-export-declaration': 7.24.7 '@babel/parser': 7.24.8 '@babel/types': 7.24.9 - debug: 4.3.7 + debug: 4.4.0 globals: 11.12.0 transitivePeerDependencies: - supports-color @@ -10545,6 +11221,18 @@ snapshots: '@babel/parser': 7.25.6 '@babel/template': 7.25.0 '@babel/types': 7.25.6 + debug: 4.4.0 + globals: 11.12.0 + transitivePeerDependencies: + - supports-color + + '@babel/traverse@7.26.8': + dependencies: + '@babel/code-frame': 7.26.2 + '@babel/generator': 7.26.8 + '@babel/parser': 7.26.8 + '@babel/template': 7.26.8 + '@babel/types': 7.26.8 debug: 4.3.7 globals: 11.12.0 transitivePeerDependencies: @@ -10562,12 +11250,19 @@ snapshots: '@babel/helper-validator-identifier': 7.24.7 to-fast-properties: 2.0.0 + '@babel/types@7.26.8': + dependencies: + '@babel/helper-string-parser': 7.25.9 + '@babel/helper-validator-identifier': 7.25.9 + '@bany/curl-to-json@1.2.8': dependencies: minimist: 1.2.8 '@bcoe/v8-coverage@0.2.3': {} + '@bcoe/v8-coverage@1.0.2': {} + '@braintree/sanitize-url@6.0.4': {} '@chakra-ui/accordion@2.3.1(@chakra-ui/system@2.6.1(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@emotion/styled@11.11.0(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(react@18.3.1))(react@18.3.1))(framer-motion@9.1.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)': @@ -10834,12 +11529,12 @@ snapshots: transitivePeerDependencies: - '@types/react' - '@chakra-ui/next-js@2.1.5(@chakra-ui/react@2.8.1(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@emotion/styled@11.11.0(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(framer-motion@9.1.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(next@14.2.5(@babel/core@7.24.9)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.8))(react@18.3.1)': + '@chakra-ui/next-js@2.1.5(@chakra-ui/react@2.8.1(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@emotion/styled@11.11.0(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(framer-motion@9.1.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(next@14.2.21(@babel/core@7.24.9)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.8))(react@18.3.1)': dependencies: '@chakra-ui/react': 2.8.1(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@emotion/styled@11.11.0(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(framer-motion@9.1.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@emotion/cache': 11.11.0 '@emotion/react': 11.11.1(@types/react@18.3.1)(react@18.3.1) - next: 14.2.5(@babel/core@7.24.9)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.8) + next: 14.2.21(@babel/core@7.24.9)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.8) react: 18.3.1 '@chakra-ui/number-input@2.1.1(@chakra-ui/system@2.6.1(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@emotion/styled@11.11.0(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(react@18.3.1))(react@18.3.1))(react@18.3.1)': @@ -11304,6 +11999,26 @@ snapshots: dependencies: '@jridgewell/trace-mapping': 0.3.9 + '@csstools/color-helpers@5.0.1': {} + + '@csstools/css-calc@2.1.1(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3)': + dependencies: + '@csstools/css-parser-algorithms': 3.0.4(@csstools/css-tokenizer@3.0.3) + '@csstools/css-tokenizer': 3.0.3 + + '@csstools/css-color-parser@3.0.7(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3)': + dependencies: + '@csstools/color-helpers': 5.0.1 + '@csstools/css-calc': 2.1.1(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3) + '@csstools/css-parser-algorithms': 3.0.4(@csstools/css-tokenizer@3.0.3) + '@csstools/css-tokenizer': 3.0.3 + + '@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3)': + dependencies: + '@csstools/css-tokenizer': 3.0.3 + + '@csstools/css-tokenizer@3.0.3': {} + '@dabh/diagnostics@2.0.3': dependencies: colorspace: 1.1.4 @@ -11313,23 +12028,23 @@ snapshots: '@emnapi/core@1.3.1': dependencies: '@emnapi/wasi-threads': 1.0.1 - tslib: 2.8.0 + tslib: 2.8.1 optional: true '@emnapi/runtime@1.3.1': dependencies: - tslib: 2.8.0 + tslib: 2.8.1 optional: true '@emnapi/wasi-threads@1.0.1': dependencies: - tslib: 2.8.0 + tslib: 2.8.1 optional: true '@emotion/babel-plugin@11.11.0': dependencies: '@babel/helper-module-imports': 7.24.7 - '@babel/runtime': 7.24.8 + '@babel/runtime': 7.25.7 '@emotion/hash': 0.9.1 '@emotion/memoize': 0.8.1 '@emotion/serialize': 1.1.4 @@ -11562,7 +12277,7 @@ snapshots: '@eslint/eslintrc@2.1.4': dependencies: ajv: 6.12.6 - debug: 4.3.5 + debug: 4.3.7 espree: 9.6.1 globals: 13.24.0 ignore: 5.3.1 @@ -11650,7 +12365,7 @@ snapshots: '@humanwhocodes/config-array@0.11.14': dependencies: '@humanwhocodes/object-schema': 2.0.3 - debug: 4.3.5 + debug: 4.3.7 minimatch: 3.1.2 transitivePeerDependencies: - supports-color @@ -11873,13 +12588,13 @@ snapshots: '@jsdevtools/ono@7.1.3': {} - '@jsep-plugin/assignment@1.2.1(jsep@1.3.9)': + '@jsep-plugin/assignment@1.3.0(jsep@1.4.0)': dependencies: - jsep: 1.3.9 + jsep: 1.4.0 - '@jsep-plugin/regex@1.0.3(jsep@1.3.9)': + '@jsep-plugin/regex@1.0.4(jsep@1.4.0)': dependencies: - jsep: 1.3.9 + jsep: 1.4.0 '@lexical/clipboard@0.12.6(lexical@0.12.6)': dependencies: @@ -12181,37 +12896,37 @@ snapshots: '@nestjs/core': 10.3.10(@nestjs/common@10.3.10(reflect-metadata@0.2.2)(rxjs@7.8.1))(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1) tslib: 2.6.3 - '@next/env@14.2.5': {} + '@next/env@14.2.21': {} '@next/eslint-plugin-next@14.2.3': dependencies: glob: 10.3.10 - '@next/swc-darwin-arm64@14.2.5': + '@next/swc-darwin-arm64@14.2.21': optional: true - '@next/swc-darwin-x64@14.2.5': + '@next/swc-darwin-x64@14.2.21': optional: true - '@next/swc-linux-arm64-gnu@14.2.5': + '@next/swc-linux-arm64-gnu@14.2.21': optional: true - '@next/swc-linux-arm64-musl@14.2.5': + '@next/swc-linux-arm64-musl@14.2.21': optional: true - '@next/swc-linux-x64-gnu@14.2.5': + '@next/swc-linux-x64-gnu@14.2.21': optional: true - '@next/swc-linux-x64-musl@14.2.5': + '@next/swc-linux-x64-musl@14.2.21': optional: true - '@next/swc-win32-arm64-msvc@14.2.5': + '@next/swc-win32-arm64-msvc@14.2.21': optional: true - '@next/swc-win32-ia32-msvc@14.2.5': + '@next/swc-win32-ia32-msvc@14.2.21': optional: true - '@next/swc-win32-x64-msvc@14.2.5': + '@next/swc-win32-x64-msvc@14.2.21': optional: true '@node-rs/jieba-android-arm-eabi@1.10.0': @@ -12469,11 +13184,11 @@ snapshots: '@sec-ant/readable-stream@0.4.1': {} - '@shelf/jest-mongodb@4.3.2(jest-environment-node@29.7.0)(mongodb@6.9.0(socks@2.8.3))': + '@shelf/jest-mongodb@4.3.2(jest-environment-node@29.7.0)(mongodb@6.13.1(socks@2.8.3))': dependencies: debug: 4.3.4 jest-environment-node: 29.7.0 - mongodb: 6.9.0(socks@2.8.3) + mongodb: 6.13.1(socks@2.8.3) mongodb-memory-server: 9.2.0 transitivePeerDependencies: - '@aws-sdk/credential-providers' @@ -12587,7 +13302,7 @@ snapshots: '@swc/helpers@0.5.5': dependencies: '@swc/counter': 0.1.3 - tslib: 2.8.0 + tslib: 2.8.1 '@tanstack/query-core@4.36.1': {} @@ -12601,6 +13316,27 @@ snapshots: '@tediousjs/connection-string@0.5.0': {} + '@testing-library/dom@10.4.0': + dependencies: + '@babel/code-frame': 7.24.7 + '@babel/runtime': 7.25.7 + '@types/aria-query': 5.0.4 + aria-query: 5.3.0 + chalk: 4.1.2 + dom-accessibility-api: 0.5.16 + lz-string: 1.5.0 + pretty-format: 27.5.1 + + '@testing-library/react@16.2.0(@testing-library/dom@10.4.0)(@types/react-dom@18.3.0)(@types/react@18.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@babel/runtime': 7.25.7 + '@testing-library/dom': 10.4.0 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.1 + '@types/react-dom': 18.3.0 + '@tokenizer/token@0.3.0': {} '@trysound/sax@0.2.0': {} @@ -12615,13 +13351,15 @@ snapshots: '@tybys/wasm-util@0.8.3': dependencies: - tslib: 2.8.0 + tslib: 2.8.1 optional: true + '@types/aria-query@5.0.4': {} + '@types/babel__core@7.20.5': dependencies: - '@babel/parser': 7.24.8 - '@babel/types': 7.24.9 + '@babel/parser': 7.25.6 + '@babel/types': 7.25.6 '@types/babel__generator': 7.6.8 '@types/babel__template': 7.4.4 '@types/babel__traverse': 7.20.6 @@ -12632,12 +13370,12 @@ snapshots: '@types/babel__template@7.4.4': dependencies: - '@babel/parser': 7.24.8 - '@babel/types': 7.24.9 + '@babel/parser': 7.25.6 + '@babel/types': 7.25.6 '@types/babel__traverse@7.20.6': dependencies: - '@babel/types': 7.24.9 + '@babel/types': 7.25.6 '@types/body-parser@1.19.5': dependencies: @@ -12813,6 +13551,8 @@ snapshots: dependencies: '@types/node': 20.14.11 + '@types/gensync@1.0.4': {} + '@types/geojson@7946.0.14': {} '@types/graceful-fs@4.1.9': @@ -12897,8 +13637,13 @@ snapshots: '@types/node-fetch@2.6.11': dependencies: +<<<<<<< HEAD '@types/node': 20.14.0 + form-data: 4.0.2 +======= + '@types/node': 20.14.11 form-data: 4.0.1 +>>>>>>> 80a0b9296 (chore: vitest) '@types/node@18.19.40': dependencies: @@ -13077,7 +13822,7 @@ snapshots: dependencies: '@typescript-eslint/typescript-estree': 6.21.0(typescript@5.5.3) '@typescript-eslint/utils': 6.21.0(eslint@8.56.0)(typescript@5.5.3) - debug: 4.3.5 + debug: 4.3.7 eslint: 8.56.0 ts-api-utils: 1.3.0(typescript@5.5.3) optionalDependencies: @@ -13123,28 +13868,91 @@ snapshots: '@ungap/structured-clone@1.2.0': {} + '@vitejs/plugin-react@4.3.4(vite@5.3.4(@types/node@22.7.8)(sass@1.77.8)(terser@5.31.3))': + dependencies: + '@babel/core': 7.26.8 + '@babel/plugin-transform-react-jsx-self': 7.25.9(@babel/core@7.26.8) + '@babel/plugin-transform-react-jsx-source': 7.25.9(@babel/core@7.26.8) + '@types/babel__core': 7.20.5 + react-refresh: 0.14.2 + vite: 5.3.4(@types/node@22.7.8)(sass@1.77.8)(terser@5.31.3) + transitivePeerDependencies: + - supports-color + + '@vitest/coverage-v8@3.0.5(vitest@3.0.5(@types/debug@4.1.12)(@types/node@22.7.8)(jsdom@26.0.0)(sass@1.77.8)(terser@5.31.3))': + dependencies: + '@ampproject/remapping': 2.3.0 + '@bcoe/v8-coverage': 1.0.2 + debug: 4.4.0 + istanbul-lib-coverage: 3.2.2 + istanbul-lib-report: 3.0.1 + istanbul-lib-source-maps: 5.0.6 + istanbul-reports: 3.1.7 + magic-string: 0.30.17 + magicast: 0.3.5 + std-env: 3.8.0 + test-exclude: 7.0.1 + tinyrainbow: 2.0.0 + vitest: 3.0.5(@types/debug@4.1.12)(@types/node@22.7.8)(jsdom@26.0.0)(sass@1.77.8)(terser@5.31.3) + transitivePeerDependencies: + - supports-color + '@vitest/expect@1.6.0': dependencies: '@vitest/spy': 1.6.0 '@vitest/utils': 1.6.0 chai: 4.4.1 + '@vitest/expect@3.0.5': + dependencies: + '@vitest/spy': 3.0.5 + '@vitest/utils': 3.0.5 + chai: 5.1.2 + tinyrainbow: 2.0.0 + + '@vitest/mocker@3.0.5(vite@5.3.4(@types/node@22.7.8)(sass@1.77.8)(terser@5.31.3))': + dependencies: + '@vitest/spy': 3.0.5 + estree-walker: 3.0.3 + magic-string: 0.30.17 + optionalDependencies: + vite: 5.3.4(@types/node@22.7.8)(sass@1.77.8)(terser@5.31.3) + + '@vitest/pretty-format@3.0.5': + dependencies: + tinyrainbow: 2.0.0 + '@vitest/runner@1.6.0': dependencies: '@vitest/utils': 1.6.0 p-limit: 5.0.0 pathe: 1.1.2 + '@vitest/runner@3.0.5': + dependencies: + '@vitest/utils': 3.0.5 + pathe: 2.0.2 + '@vitest/snapshot@1.6.0': dependencies: - magic-string: 0.30.10 + magic-string: 0.30.17 pathe: 1.1.2 pretty-format: 29.7.0 + '@vitest/snapshot@3.0.5': + dependencies: + '@vitest/pretty-format': 3.0.5 + magic-string: 0.30.17 + pathe: 2.0.2 + '@vitest/spy@1.6.0': dependencies: tinyspy: 2.2.1 + '@vitest/spy@3.0.5': + dependencies: + tinyspy: 3.0.2 + '@vitest/utils@1.6.0': dependencies: diff-sequences: 29.6.3 @@ -13152,6 +13960,12 @@ snapshots: loupe: 2.3.7 pretty-format: 29.7.0 + '@vitest/utils@3.0.5': + dependencies: + '@vitest/pretty-format': 3.0.5 + loupe: 3.1.3 + tinyrainbow: 2.0.0 + '@vue/compiler-core@3.4.32': dependencies: '@babel/parser': 7.24.8 @@ -13173,9 +13987,15 @@ snapshots: '@vue/compiler-ssr': 3.4.32 '@vue/shared': 3.4.32 estree-walker: 2.0.2 +<<<<<<< HEAD magic-string: 0.30.10 + postcss: 8.5.3 + source-map-js: 1.2.1 +======= + magic-string: 0.30.17 postcss: 8.4.39 source-map-js: 1.2.0 +>>>>>>> 80a0b9296 (chore: vitest) '@vue/compiler-ssr@3.4.32': dependencies: @@ -13339,17 +14159,19 @@ snapshots: agent-base@6.0.2: dependencies: - debug: 4.3.7 + debug: 4.4.0 transitivePeerDependencies: - supports-color optional: true agent-base@7.1.1: dependencies: - debug: 4.3.7 + debug: 4.4.0 transitivePeerDependencies: - supports-color + agent-base@7.1.3: {} + agentkeepalive@4.5.0: dependencies: humanize-ms: 1.2.1 @@ -13465,12 +14287,16 @@ snapshots: aria-hidden@1.2.4: dependencies: - tslib: 2.6.3 + tslib: 2.8.1 aria-query@5.1.3: dependencies: deep-equal: 2.2.3 + aria-query@5.3.0: + dependencies: + dequal: 2.0.3 + array-buffer-byte-length@1.0.1: dependencies: call-bind: 1.0.7 @@ -13545,7 +14371,7 @@ snapshots: define-properties: 1.2.1 es-abstract: 1.23.3 es-errors: 1.3.0 - get-intrinsic: 1.2.4 + get-intrinsic: 1.3.0 is-array-buffer: 3.0.4 is-shared-array-buffer: 1.0.3 @@ -13553,15 +14379,17 @@ snapshots: assertion-error@1.1.0: {} + assertion-error@2.0.1: {} + ast-types-flow@0.0.8: {} async-mutex@0.4.1: dependencies: - tslib: 2.8.0 + tslib: 2.8.1 async-mutex@0.5.0: dependencies: - tslib: 2.7.0 + tslib: 2.8.1 async@3.2.5: {} @@ -13584,18 +14412,15 @@ snapshots: axe-core@4.9.1: {} - axios@1.7.2: - dependencies: - follow-redirects: 1.15.6 - form-data: 4.0.1 - proxy-from-env: 1.1.0 - transitivePeerDependencies: - - debug - - axios@1.7.7: + axios@1.8.2: dependencies: +<<<<<<< HEAD follow-redirects: 1.15.9(debug@4.3.7) + form-data: 4.0.2 +======= + follow-redirects: 1.15.9(debug@4.4.0) form-data: 4.0.1 +>>>>>>> 80a0b9296 (chore: vitest) proxy-from-env: 1.1.0 transitivePeerDependencies: - debug @@ -13631,14 +14456,14 @@ snapshots: babel-plugin-jest-hoist@29.6.3: dependencies: - '@babel/template': 7.24.7 - '@babel/types': 7.24.9 + '@babel/template': 7.25.0 + '@babel/types': 7.25.6 '@types/babel__core': 7.20.5 '@types/babel__traverse': 7.20.6 babel-plugin-macros@3.1.0: dependencies: - '@babel/runtime': 7.24.8 + '@babel/runtime': 7.25.7 cosmiconfig: 7.1.0 resolve: 1.22.8 @@ -13757,11 +14582,18 @@ snapshots: browserslist@4.23.2: dependencies: - caniuse-lite: 1.0.30001669 + caniuse-lite: 1.0.30001703 electron-to-chromium: 1.4.829 node-releases: 2.0.17 update-browserslist-db: 1.1.0(browserslist@4.23.2) + browserslist@4.24.4: + dependencies: + caniuse-lite: 1.0.30001698 + electron-to-chromium: 1.5.96 + node-releases: 2.0.19 + update-browserslist-db: 1.1.2(browserslist@4.24.4) + bs-logger@0.2.6: dependencies: fast-json-stable-stringify: 2.1.0 @@ -13772,6 +14604,8 @@ snapshots: bson@5.5.1: {} + bson@6.10.3: {} + bson@6.8.0: {} buffer-alloc-unsafe@1.1.0: {} @@ -13822,6 +14656,11 @@ snapshots: tar: 6.2.1 unique-filename: 3.0.0 + call-bind-apply-helpers@1.0.2: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + call-bind@1.0.7: dependencies: es-define-property: 1.0.0 @@ -13840,7 +14679,9 @@ snapshots: camelcase@6.3.0: {} - caniuse-lite@1.0.30001669: {} + caniuse-lite@1.0.30001703: {} + + caniuse-lite@1.0.30001698: {} canvas@2.11.2(encoding@0.1.13): dependencies: @@ -13864,6 +14705,14 @@ snapshots: pathval: 1.1.1 type-detect: 4.0.8 + chai@5.1.2: + dependencies: + assertion-error: 2.0.1 + check-error: 2.1.1 + deep-eql: 5.0.2 + loupe: 3.1.3 + pathval: 2.0.0 + chalk@2.4.2: dependencies: ansi-styles: 3.2.1 @@ -13904,6 +14753,8 @@ snapshots: dependencies: get-func-name: 2.0.2 + check-error@2.1.1: {} + cheerio-select@2.1.0: dependencies: boolbase: 1.0.0 @@ -14227,6 +15078,11 @@ snapshots: dependencies: css-tree: 1.1.3 + cssstyle@4.2.1: + dependencies: + '@asamuzakjp/css-color': 2.8.3 + rrweb-cssom: 0.8.0 + csstype@3.1.3: {} cytoscape-cose-bilkent@4.1.0(cytoscape@3.30.0): @@ -14410,6 +15266,11 @@ snapshots: damerau-levenshtein@1.0.8: {} + data-urls@5.0.0: + dependencies: + whatwg-mimetype: 4.0.0 + whatwg-url: 14.1.0 + data-view-buffer@1.0.1: dependencies: call-bind: 1.0.7 @@ -14456,10 +15317,16 @@ snapshots: dependencies: ms: 2.1.3 + debug@4.4.0: + dependencies: + ms: 2.1.3 + decamelize@1.2.0: {} decimal.js-light@2.5.1: {} + decimal.js@10.5.0: {} + decode-named-character-reference@1.0.2: dependencies: character-entities: 2.0.2 @@ -14519,12 +15386,14 @@ snapshots: dependencies: type-detect: 4.0.8 + deep-eql@5.0.2: {} + deep-equal@2.2.3: dependencies: array-buffer-byte-length: 1.0.1 call-bind: 1.0.7 es-get-iterator: 1.1.3 - get-intrinsic: 1.2.4 + get-intrinsic: 1.3.0 is-arguments: 1.1.1 is-array-buffer: 3.0.4 is-date-object: 1.0.5 @@ -14552,9 +15421,9 @@ snapshots: define-data-property@1.1.4: dependencies: - es-define-property: 1.0.0 + es-define-property: 1.0.1 es-errors: 1.3.0 - gopd: 1.0.1 + gopd: 1.2.0 define-lazy-prop@2.0.0: {} @@ -14620,6 +15489,8 @@ snapshots: dependencies: esutils: 2.0.3 + dom-accessibility-api@0.5.16: {} + dom-helpers@5.2.1: dependencies: '@babel/runtime': 7.25.7 @@ -14672,6 +15543,12 @@ snapshots: dependencies: underscore: 1.13.7 + dunder-proto@1.0.1: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-errors: 1.3.0 + gopd: 1.2.0 + eastasianwidth@0.2.0: {} ecdsa-sig-formatter@1.0.11: @@ -14697,6 +15574,8 @@ snapshots: electron-to-chromium@1.4.829: {} + electron-to-chromium@1.5.96: {} + elkjs@0.9.3: {} emittery@0.13.1: {} @@ -14748,10 +15627,10 @@ snapshots: es-define-property: 1.0.0 es-errors: 1.3.0 es-object-atoms: 1.0.0 - es-set-tostringtag: 2.0.3 + es-set-tostringtag: 2.1.0 es-to-primitive: 1.2.1 function.prototype.name: 1.1.6 - get-intrinsic: 1.2.4 + get-intrinsic: 1.3.0 get-symbol-description: 1.0.2 globalthis: 1.0.4 gopd: 1.0.1 @@ -14787,15 +15666,17 @@ snapshots: es-define-property@1.0.0: dependencies: - get-intrinsic: 1.2.4 + get-intrinsic: 1.3.0 + + es-define-property@1.0.1: {} es-errors@1.3.0: {} es-get-iterator@1.1.3: dependencies: call-bind: 1.0.7 - get-intrinsic: 1.2.4 - has-symbols: 1.0.3 + get-intrinsic: 1.3.0 + has-symbols: 1.1.0 is-arguments: 1.1.1 is-map: 2.0.3 is-set: 2.0.3 @@ -14809,7 +15690,7 @@ snapshots: define-properties: 1.2.1 es-abstract: 1.23.3 es-errors: 1.3.0 - es-set-tostringtag: 2.0.3 + es-set-tostringtag: 2.1.0 function-bind: 1.1.2 get-intrinsic: 1.2.4 globalthis: 1.0.4 @@ -14822,13 +15703,20 @@ snapshots: es-module-lexer@1.5.4: {} + es-module-lexer@1.6.0: {} + es-object-atoms@1.0.0: dependencies: es-errors: 1.3.0 - es-set-tostringtag@2.0.3: + es-object-atoms@1.1.1: dependencies: - get-intrinsic: 1.2.4 + es-errors: 1.3.0 + + es-set-tostringtag@2.1.0: + dependencies: + es-errors: 1.3.0 + get-intrinsic: 1.3.0 has-tostringtag: 1.0.2 hasown: 2.0.2 @@ -14895,6 +15783,8 @@ snapshots: escalade@3.1.2: {} + escalade@3.2.0: {} + escape-html@1.0.3: {} escape-string-regexp@1.0.5: {} @@ -14913,7 +15803,7 @@ snapshots: eslint: 8.56.0 eslint-import-resolver-node: 0.3.9 eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.56.0)(typescript@5.5.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(eslint@8.56.0))(eslint@8.56.0) - eslint-plugin-import: 2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.56.0)(typescript@5.5.3))(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.56.0)(typescript@5.5.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(eslint@8.56.0))(eslint@8.56.0))(eslint@8.56.0) + eslint-plugin-import: 2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.56.0)(typescript@5.5.3))(eslint-import-resolver-typescript@3.6.1)(eslint@8.56.0) eslint-plugin-jsx-a11y: 6.9.0(eslint@8.56.0) eslint-plugin-react: 7.34.4(eslint@8.56.0) eslint-plugin-react-hooks: 4.6.2(eslint@8.56.0) @@ -14933,11 +15823,19 @@ snapshots: eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.56.0)(typescript@5.5.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(eslint@8.56.0))(eslint@8.56.0): dependencies: - debug: 4.3.7 + debug: 4.4.0 enhanced-resolve: 5.17.0 eslint: 8.56.0 +<<<<<<< HEAD + eslint-module-utils: 2.8.1(@typescript-eslint/parser@6.21.0(eslint@8.56.0)(typescript@5.5.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@8.56.0) +======= +<<<<<<< HEAD eslint-module-utils: 2.8.1(@typescript-eslint/parser@6.21.0(eslint@8.56.0)(typescript@5.5.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.56.0)(typescript@5.5.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(eslint@8.56.0))(eslint@8.56.0))(eslint@8.56.0) - eslint-plugin-import: 2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.56.0)(typescript@5.5.3))(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.56.0)(typescript@5.5.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(eslint@8.56.0))(eslint@8.56.0))(eslint@8.56.0) +======= + eslint-module-utils: 2.8.1(@typescript-eslint/parser@6.21.0(eslint@8.56.0)(typescript@5.5.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@8.56.0) +>>>>>>> c2222dcc1 (chore: vitest) +>>>>>>> 80a0b9296 (chore: vitest) + eslint-plugin-import: 2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.56.0)(typescript@5.5.3))(eslint-import-resolver-typescript@3.6.1)(eslint@8.56.0) fast-glob: 3.3.2 get-tsconfig: 4.7.5 is-core-module: 2.14.0 @@ -14948,7 +15846,7 @@ snapshots: - eslint-import-resolver-webpack - supports-color - eslint-module-utils@2.8.1(@typescript-eslint/parser@6.21.0(eslint@8.56.0)(typescript@5.5.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.56.0)(typescript@5.5.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(eslint@8.56.0))(eslint@8.56.0))(eslint@8.56.0): + eslint-module-utils@2.8.1(@typescript-eslint/parser@6.21.0(eslint@8.56.0)(typescript@5.5.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@8.56.0): dependencies: debug: 3.2.7 optionalDependencies: @@ -14959,7 +15857,7 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-plugin-import@2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.56.0)(typescript@5.5.3))(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.56.0)(typescript@5.5.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(eslint@8.56.0))(eslint@8.56.0))(eslint@8.56.0): + eslint-plugin-import@2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.56.0)(typescript@5.5.3))(eslint-import-resolver-typescript@3.6.1)(eslint@8.56.0): dependencies: array-includes: 3.1.8 array.prototype.findlastindex: 1.2.5 @@ -14969,7 +15867,7 @@ snapshots: doctrine: 2.1.0 eslint: 8.56.0 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.8.1(@typescript-eslint/parser@6.21.0(eslint@8.56.0)(typescript@5.5.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.56.0)(typescript@5.5.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(eslint@8.56.0))(eslint@8.56.0))(eslint@8.56.0) + eslint-module-utils: 2.8.1(@typescript-eslint/parser@6.21.0(eslint@8.56.0)(typescript@5.5.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@8.56.0) hasown: 2.0.2 is-core-module: 2.14.0 is-glob: 4.0.3 @@ -15168,6 +16066,8 @@ snapshots: expand-template@2.0.3: {} + expect-type@1.1.0: {} + expect@29.7.0: dependencies: '@jest/expect-utils': 29.7.0 @@ -15403,13 +16303,11 @@ snapshots: focus-lock@1.3.5: dependencies: - tslib: 2.8.0 + tslib: 2.8.1 - follow-redirects@1.15.6: {} - - follow-redirects@1.15.9(debug@4.3.7): + follow-redirects@1.15.9(debug@4.4.0): optionalDependencies: - debug: 4.3.7 + debug: 4.4.0 for-each@0.3.3: dependencies: @@ -15445,6 +16343,13 @@ snapshots: combined-stream: 1.0.8 mime-types: 2.1.35 + form-data@4.0.2: + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + es-set-tostringtag: 2.1.0 + mime-types: 2.1.35 + format@0.2.2: {} formdata-node@4.4.1: @@ -15542,10 +16447,28 @@ snapshots: has-symbols: 1.0.3 hasown: 2.0.2 + get-intrinsic@1.3.0: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + function-bind: 1.1.2 + get-proto: 1.0.1 + gopd: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.2 + math-intrinsics: 1.1.0 + get-nonce@1.0.1: {} get-package-type@0.1.0: {} + get-proto@1.0.1: + dependencies: + dunder-proto: 1.0.1 + es-object-atoms: 1.1.1 + get-stream@2.3.1: dependencies: object-assign: 4.1.1 @@ -15564,7 +16487,7 @@ snapshots: dependencies: call-bind: 1.0.7 es-errors: 1.3.0 - get-intrinsic: 1.2.4 + get-intrinsic: 1.3.0 get-tsconfig@4.7.5: dependencies: @@ -15637,9 +16560,13 @@ snapshots: merge2: 1.4.1 slash: 3.0.0 + globrex@0.1.2: {} + gopd@1.0.1: dependencies: - get-intrinsic: 1.2.4 + get-intrinsic: 1.3.0 + + gopd@1.2.0: {} graceful-fs@4.2.11: {} @@ -15661,9 +16588,11 @@ snapshots: has-symbols@1.0.3: {} + has-symbols@1.1.0: {} + has-tostringtag@1.0.2: dependencies: - has-symbols: 1.0.3 + has-symbols: 1.1.0 has-unicode@2.0.1: optional: true @@ -15770,6 +16699,10 @@ snapshots: dependencies: react-is: 16.13.1 + html-encoding-sniffer@4.0.0: + dependencies: + whatwg-encoding: 3.1.1 + html-entities@2.5.2: {} html-escaper@2.0.2: {} @@ -15800,14 +16733,14 @@ snapshots: http-proxy-agent@7.0.2: dependencies: agent-base: 7.1.1 - debug: 4.3.7 + debug: 4.4.0 transitivePeerDependencies: - supports-color https-proxy-agent@5.0.1: dependencies: agent-base: 6.0.2 - debug: 4.3.7 + debug: 4.4.0 transitivePeerDependencies: - supports-color optional: true @@ -15815,7 +16748,14 @@ snapshots: https-proxy-agent@7.0.5: dependencies: agent-base: 7.1.1 - debug: 4.3.7 + debug: 4.4.0 + transitivePeerDependencies: + - supports-color + + https-proxy-agent@7.0.6: + dependencies: + agent-base: 7.1.3 + debug: 4.4.0 transitivePeerDependencies: - supports-color @@ -15971,7 +16911,7 @@ snapshots: is-array-buffer@3.0.4: dependencies: call-bind: 1.0.7 - get-intrinsic: 1.2.4 + get-intrinsic: 1.3.0 is-arrayish@0.2.1: {} @@ -16062,6 +17002,8 @@ snapshots: is-plain-obj@4.1.0: {} + is-potential-custom-element-name@1.0.1: {} + is-property@1.0.2: {} is-regex@1.1.4: @@ -16089,7 +17031,7 @@ snapshots: is-symbol@1.0.4: dependencies: - has-symbols: 1.0.3 + has-symbols: 1.1.0 is-typed-array@1.1.13: dependencies: @@ -16106,7 +17048,7 @@ snapshots: is-weakset@2.0.3: dependencies: call-bind: 1.0.7 - get-intrinsic: 1.2.4 + get-intrinsic: 1.3.0 is-whitespace-character@1.0.4: {} @@ -16135,7 +17077,7 @@ snapshots: istanbul-lib-instrument@5.2.1: dependencies: '@babel/core': 7.24.9 - '@babel/parser': 7.24.8 + '@babel/parser': 7.25.6 '@istanbuljs/schema': 0.1.3 istanbul-lib-coverage: 3.2.2 semver: 6.3.1 @@ -16145,7 +17087,7 @@ snapshots: istanbul-lib-instrument@6.0.3: dependencies: '@babel/core': 7.24.9 - '@babel/parser': 7.24.8 + '@babel/parser': 7.25.6 '@istanbuljs/schema': 0.1.3 istanbul-lib-coverage: 3.2.2 semver: 7.6.3 @@ -16160,12 +17102,20 @@ snapshots: istanbul-lib-source-maps@4.0.1: dependencies: - debug: 4.3.7 + debug: 4.4.0 istanbul-lib-coverage: 3.2.2 source-map: 0.6.1 transitivePeerDependencies: - supports-color + istanbul-lib-source-maps@5.0.6: + dependencies: + '@jridgewell/trace-mapping': 0.3.25 + debug: 4.4.0 + istanbul-lib-coverage: 3.2.2 + transitivePeerDependencies: + - supports-color + istanbul-reports@3.1.7: dependencies: html-escaper: 2.0.2 @@ -16176,7 +17126,7 @@ snapshots: iterator.prototype@1.1.2: dependencies: define-properties: 1.2.1 - get-intrinsic: 1.2.4 + get-intrinsic: 1.3.0 has-symbols: 1.0.3 reflect.getprototypeof: 1.0.6 set-function-name: 2.0.2 @@ -16540,12 +17490,46 @@ snapshots: jschardet@3.1.1: {} +<<<<<<< HEAD + jsep@1.4.0: {} +======= + jsdom@26.0.0: + dependencies: + cssstyle: 4.2.1 + data-urls: 5.0.0 + decimal.js: 10.5.0 + form-data: 4.0.1 + html-encoding-sniffer: 4.0.0 + http-proxy-agent: 7.0.2 + https-proxy-agent: 7.0.6 + is-potential-custom-element-name: 1.0.1 + nwsapi: 2.2.16 + parse5: 7.2.1 + rrweb-cssom: 0.8.0 + saxes: 6.0.0 + symbol-tree: 3.2.4 + tough-cookie: 5.1.1 + w3c-xmlserializer: 5.0.0 + webidl-conversions: 7.0.0 + whatwg-encoding: 3.1.1 + whatwg-mimetype: 4.0.0 + whatwg-url: 14.1.0 + ws: 8.18.0 + xml-name-validator: 5.0.0 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + jsep@1.3.9: {} +>>>>>>> 80a0b9296 (chore: vitest) jsesc@0.5.0: {} jsesc@2.5.2: {} + jsesc@3.1.0: {} + json-buffer@3.0.1: {} json-parse-even-better-errors@2.3.1: {} @@ -16582,11 +17566,11 @@ snapshots: optionalDependencies: graceful-fs: 4.2.11 - jsonpath-plus@10.1.0: + jsonpath-plus@10.3.0: dependencies: - '@jsep-plugin/assignment': 1.2.1(jsep@1.3.9) - '@jsep-plugin/regex': 1.0.3(jsep@1.3.9) - jsep: 1.3.9 + '@jsep-plugin/assignment': 1.3.0(jsep@1.4.0) + '@jsep-plugin/regex': 1.0.4(jsep@1.4.0) + jsep: 1.4.0 jsonwebtoken@9.0.2: dependencies: @@ -16637,7 +17621,7 @@ snapshots: jwa: 2.0.0 safe-buffer: 5.2.1 - kareem@2.5.1: {} + kareem@2.6.3: {} katex@0.16.11: dependencies: @@ -16674,7 +17658,7 @@ snapshots: lexical@0.14.5: {} - lib0@0.2.98: + lib0@0.2.99: dependencies: isomorphic.js: 0.2.5 @@ -16810,6 +17794,8 @@ snapshots: dependencies: get-func-name: 2.0.2 + loupe@3.1.3: {} + lowlight@1.20.0: dependencies: fault: 1.0.4 @@ -16829,14 +17815,26 @@ snapshots: luxon@3.4.4: {} + lz-string@1.5.0: {} + magic-string@0.30.10: dependencies: '@jridgewell/sourcemap-codec': 1.5.0 + magic-string@0.30.17: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.0 + magic-string@0.30.8: dependencies: '@jridgewell/sourcemap-codec': 1.5.0 + magicast@0.3.5: + dependencies: + '@babel/parser': 7.26.8 + '@babel/types': 7.26.8 + source-map-js: 1.2.1 + make-dir@1.3.0: dependencies: pify: 3.0.0 @@ -16889,6 +17887,8 @@ snapshots: markdown-table@3.0.3: {} + math-intrinsics@1.1.0: {} + mdast-util-find-and-replace@3.0.1: dependencies: '@types/mdast': 4.0.4 @@ -17418,7 +18418,7 @@ snapshots: micromark@3.2.0: dependencies: '@types/debug': 4.1.12 - debug: 4.3.7 + debug: 4.4.0 decode-named-character-reference: 1.0.2 micromark-core-commonmark: 1.1.0 micromark-factory-space: 1.1.0 @@ -17440,7 +18440,7 @@ snapshots: micromark@4.0.0: dependencies: '@types/debug': 4.1.12 - debug: 4.3.7 + debug: 4.4.0 decode-named-character-reference: 1.0.2 devlop: 1.1.0 micromark-core-commonmark: 2.0.1 @@ -17564,9 +18564,9 @@ snapshots: dependencies: obliterator: 2.0.4 - mockingoose@2.16.2(mongoose@7.8.2): + mockingoose@2.16.2(mongoose@8.10.2(socks@2.8.3)): dependencies: - mongoose: 7.8.2 + mongoose: 8.10.2(socks@2.8.3) monaco-editor@0.50.0: {} @@ -17584,9 +18584,9 @@ snapshots: dependencies: async-mutex: 0.5.0 camelcase: 6.3.0 - debug: 4.3.7 + debug: 4.4.0 find-cache-dir: 3.3.2 - follow-redirects: 1.15.9(debug@4.3.7) + follow-redirects: 1.15.9(debug@4.4.0) https-proxy-agent: 7.0.5 mongodb: 6.9.0(socks@2.8.3) new-find-package-json: 2.0.0 @@ -17604,19 +18604,43 @@ snapshots: - socks - supports-color + mongodb-memory-server-core@10.1.3(socks@2.8.3): + dependencies: + async-mutex: 0.5.0 + camelcase: 6.3.0 + debug: 4.4.0 + find-cache-dir: 3.3.2 + follow-redirects: 1.15.9(debug@4.4.0) + https-proxy-agent: 7.0.6 + mongodb: 6.9.0(socks@2.8.3) + new-find-package-json: 2.0.0 + semver: 7.6.3 + tar-stream: 3.1.7 + tslib: 2.8.0 + yauzl: 3.1.3 + transitivePeerDependencies: + - '@aws-sdk/credential-providers' + - '@mongodb-js/zstd' + - gcp-metadata + - kerberos + - mongodb-client-encryption + - snappy + - socks + - supports-color + mongodb-memory-server-core@9.2.0: dependencies: async-mutex: 0.4.1 camelcase: 6.3.0 - debug: 4.3.7 + debug: 4.4.0 find-cache-dir: 3.3.2 - follow-redirects: 1.15.9(debug@4.3.7) + follow-redirects: 1.15.9(debug@4.4.0) https-proxy-agent: 7.0.5 mongodb: 5.9.2 new-find-package-json: 2.0.0 semver: 7.6.3 tar-stream: 3.1.7 - tslib: 2.6.3 + tslib: 2.8.1 yauzl: 3.1.3 transitivePeerDependencies: - '@aws-sdk/credential-providers' @@ -17640,6 +18664,20 @@ snapshots: - socks - supports-color + mongodb-memory-server@10.1.3(socks@2.8.3): + dependencies: + mongodb-memory-server-core: 10.1.3(socks@2.8.3) + tslib: 2.8.0 + transitivePeerDependencies: + - '@aws-sdk/credential-providers' + - '@mongodb-js/zstd' + - gcp-metadata + - kerberos + - mongodb-client-encryption + - snappy + - socks + - supports-color + mongodb-memory-server@9.2.0: dependencies: mongodb-memory-server-core: 9.2.0 @@ -17660,6 +18698,14 @@ snapshots: optionalDependencies: '@mongodb-js/saslprep': 1.1.9 + mongodb@6.13.1(socks@2.8.3): + dependencies: + '@mongodb-js/saslprep': 1.1.9 + bson: 6.10.3 + mongodb-connection-string-url: 3.0.1 + optionalDependencies: + socks: 2.8.3 + mongodb@6.9.0(socks@2.8.3): dependencies: '@mongodb-js/saslprep': 1.1.9 @@ -17668,21 +18714,23 @@ snapshots: optionalDependencies: socks: 2.8.3 - mongoose@7.8.2: + mongoose@8.10.2(socks@2.8.3): dependencies: - bson: 5.5.1 - kareem: 2.5.1 - mongodb: 5.9.2 + bson: 6.10.3 + kareem: 2.6.3 + mongodb: 6.13.1(socks@2.8.3) mpath: 0.9.0 mquery: 5.0.0 ms: 2.1.3 - sift: 16.0.1 + sift: 17.1.3 transitivePeerDependencies: - '@aws-sdk/credential-providers' - '@mongodb-js/zstd' + - gcp-metadata - kerberos - mongodb-client-encryption - snappy + - socks - supports-color mpath@0.9.0: {} @@ -17745,7 +18793,7 @@ snapshots: nan@2.22.0: optional: true - nanoid@3.3.7: {} + nanoid@3.3.9: {} nanoid@4.0.2: {} @@ -17766,11 +18814,11 @@ snapshots: new-find-package-json@2.0.0: dependencies: - debug: 4.3.7 + debug: 4.4.0 transitivePeerDependencies: - supports-color - next-i18next@15.3.0(i18next@23.11.5)(next@14.2.5(@babel/core@7.24.9)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.8))(react-i18next@14.1.2(i18next@23.11.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1): + next-i18next@15.3.0(i18next@23.11.5)(next@14.2.21(@babel/core@7.24.9)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.8))(react-i18next@14.1.2(i18next@23.11.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1): dependencies: '@babel/runtime': 7.24.8 '@types/hoist-non-react-statics': 3.3.5 @@ -17778,40 +18826,40 @@ snapshots: hoist-non-react-statics: 3.3.2 i18next: 23.11.5 i18next-fs-backend: 2.3.1 - next: 14.2.5(@babel/core@7.24.9)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.8) + next: 14.2.21(@babel/core@7.24.9)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.8) react: 18.3.1 react-i18next: 14.1.2(i18next@23.11.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - next@14.2.5(@babel/core@7.24.9)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.8): + next@14.2.21(@babel/core@7.24.9)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.8): dependencies: - '@next/env': 14.2.5 + '@next/env': 14.2.21 '@swc/helpers': 0.5.5 busboy: 1.6.0 - caniuse-lite: 1.0.30001669 + caniuse-lite: 1.0.30001703 graceful-fs: 4.2.11 postcss: 8.4.31 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) styled-jsx: 5.1.1(@babel/core@7.24.9)(babel-plugin-macros@3.1.0)(react@18.3.1) optionalDependencies: - '@next/swc-darwin-arm64': 14.2.5 - '@next/swc-darwin-x64': 14.2.5 - '@next/swc-linux-arm64-gnu': 14.2.5 - '@next/swc-linux-arm64-musl': 14.2.5 - '@next/swc-linux-x64-gnu': 14.2.5 - '@next/swc-linux-x64-musl': 14.2.5 - '@next/swc-win32-arm64-msvc': 14.2.5 - '@next/swc-win32-ia32-msvc': 14.2.5 - '@next/swc-win32-x64-msvc': 14.2.5 + '@next/swc-darwin-arm64': 14.2.21 + '@next/swc-darwin-x64': 14.2.21 + '@next/swc-linux-arm64-gnu': 14.2.21 + '@next/swc-linux-arm64-musl': 14.2.21 + '@next/swc-linux-x64-gnu': 14.2.21 + '@next/swc-linux-x64-musl': 14.2.21 + '@next/swc-win32-arm64-msvc': 14.2.21 + '@next/swc-win32-ia32-msvc': 14.2.21 + '@next/swc-win32-x64-msvc': 14.2.21 sass: 1.77.8 transitivePeerDependencies: - '@babel/core' - babel-plugin-macros - nextjs-cors@2.2.0(next@14.2.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.8)): + nextjs-cors@2.2.0(next@14.2.21(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.8)): dependencies: cors: 2.8.5 - next: 14.2.5(@babel/core@7.24.9)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.8) + next: 14.2.21(@babel/core@7.24.9)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.8) nextjs-node-loader@1.1.5(webpack@5.92.1): dependencies: @@ -17859,6 +18907,8 @@ snapshots: node-releases@2.0.17: {} + node-releases@2.0.19: {} + node-xlsx@0.24.0: dependencies: xlsx: https://cdn.sheetjs.com/xlsx-0.20.2/xlsx-0.20.2.tgz @@ -17898,6 +18948,8 @@ snapshots: dependencies: boolbase: 1.0.0 + nwsapi@2.2.16: {} + object-assign@4.1.1: {} object-inspect@1.13.2: {} @@ -18095,6 +19147,10 @@ snapshots: dependencies: entities: 4.5.0 + parse5@7.2.1: + dependencies: + entities: 4.5.0 + parseurl@1.3.3: {} path-exists@4.0.0: {} @@ -18125,8 +19181,12 @@ snapshots: pathe@1.1.2: {} + pathe@2.0.2: {} + pathval@1.1.1: {} + pathval@2.0.0: {} + pdfjs-dist@4.4.168(encoding@0.1.13): optionalDependencies: canvas: 2.11.2(encoding@0.1.13) @@ -18247,14 +19307,18 @@ snapshots: postcss@8.4.31: dependencies: - nanoid: 3.3.7 + nanoid: 3.3.9 picocolors: 1.1.1 source-map-js: 1.2.1 - postcss@8.4.39: + postcss@8.5.3: dependencies: +<<<<<<< HEAD + nanoid: 3.3.9 +======= nanoid: 3.3.7 - picocolors: 1.0.1 +>>>>>>> 80a0b9296 (chore: vitest) + picocolors: 1.1.1 source-map-js: 1.2.1 postgres-array@2.0.0: {} @@ -18300,6 +19364,12 @@ snapshots: prettier@3.2.4: {} + pretty-format@27.5.1: + dependencies: + ansi-regex: 5.0.1 + ansi-styles: 5.2.0 + react-is: 17.0.2 + pretty-format@29.7.0: dependencies: '@jest/schemas': 29.6.3 @@ -18505,7 +19575,7 @@ snapshots: react-redux@7.2.9(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: - '@babel/runtime': 7.24.8 + '@babel/runtime': 7.25.7 '@types/react-redux': 7.1.33 hoist-non-react-statics: 3.3.2 loose-envify: 1.4.0 @@ -18515,11 +19585,13 @@ snapshots: optionalDependencies: react-dom: 18.3.1(react@18.3.1) + react-refresh@0.14.2: {} + react-remove-scroll-bar@2.3.6(@types/react@18.3.1)(react@18.3.1): dependencies: react: 18.3.1 react-style-singleton: 2.2.1(@types/react@18.3.1)(react@18.3.1) - tslib: 2.8.0 + tslib: 2.8.1 optionalDependencies: '@types/react': 18.3.1 @@ -18528,7 +19600,7 @@ snapshots: react: 18.3.1 react-remove-scroll-bar: 2.3.6(@types/react@18.3.1)(react@18.3.1) react-style-singleton: 2.2.1(@types/react@18.3.1)(react@18.3.1) - tslib: 2.6.3 + tslib: 2.8.1 use-callback-ref: 1.3.2(@types/react@18.3.1)(react@18.3.1) use-sidecar: 1.1.2(@types/react@18.3.1)(react@18.3.1) optionalDependencies: @@ -18547,7 +19619,7 @@ snapshots: get-nonce: 1.0.1 invariant: 2.2.4 react: 18.3.1 - tslib: 2.8.0 + tslib: 2.8.1 optionalDependencies: '@types/react': 18.3.1 @@ -18645,7 +19717,7 @@ snapshots: redux@4.2.1: dependencies: - '@babel/runtime': 7.24.8 + '@babel/runtime': 7.25.7 reflect-metadata@0.2.2: {} @@ -18655,7 +19727,7 @@ snapshots: define-properties: 1.2.1 es-abstract: 1.23.3 es-errors: 1.3.0 - get-intrinsic: 1.2.4 + get-intrinsic: 1.3.0 globalthis: 1.0.4 which-builtin-type: 1.1.3 @@ -18870,6 +19942,8 @@ snapshots: '@rollup/rollup-win32-x64-msvc': 4.18.1 fsevents: 2.3.3 + rrweb-cssom@0.8.0: {} + run-async@2.4.1: {} run-async@3.0.0: {} @@ -18891,7 +19965,7 @@ snapshots: safe-array-concat@1.1.2: dependencies: call-bind: 1.0.7 - get-intrinsic: 1.2.4 + get-intrinsic: 1.3.0 has-symbols: 1.0.3 isarray: 2.0.5 @@ -18923,6 +19997,10 @@ snapshots: sax@1.4.1: {} + saxes@6.0.0: + dependencies: + xmlchars: 2.2.0 + scheduler@0.23.2: dependencies: loose-envify: 1.4.0 @@ -18987,7 +20065,7 @@ snapshots: define-data-property: 1.1.4 es-errors: 1.3.0 function-bind: 1.1.2 - get-intrinsic: 1.2.4 + get-intrinsic: 1.3.0 gopd: 1.0.1 has-property-descriptors: 1.0.2 @@ -19015,7 +20093,7 @@ snapshots: get-intrinsic: 1.2.4 object-inspect: 1.13.2 - sift@16.0.1: {} + sift@17.1.3: {} siginfo@2.0.0: {} @@ -19056,7 +20134,7 @@ snapshots: socks-proxy-agent@8.0.4: dependencies: agent-base: 7.1.1 - debug: 4.3.7 + debug: 4.4.0 socks: 2.8.3 transitivePeerDependencies: - supports-color @@ -19070,8 +20148,6 @@ snapshots: dependencies: atomic-sleep: 1.0.0 - source-map-js@1.2.0: {} - source-map-js@1.2.1: {} source-map-support@0.5.13: @@ -19128,6 +20204,8 @@ snapshots: std-env@3.7.0: {} + std-env@3.8.0: {} + stop-iteration-iterator@1.0.0: dependencies: internal-slot: 1.0.7 @@ -19193,19 +20271,19 @@ snapshots: call-bind: 1.0.7 define-properties: 1.2.1 es-abstract: 1.23.3 - es-object-atoms: 1.0.0 + es-object-atoms: 1.1.1 string.prototype.trimend@1.0.8: dependencies: call-bind: 1.0.7 define-properties: 1.2.1 - es-object-atoms: 1.0.0 + es-object-atoms: 1.1.1 string.prototype.trimstart@1.0.8: dependencies: call-bind: 1.0.7 define-properties: 1.2.1 - es-object-atoms: 1.0.0 + es-object-atoms: 1.1.1 string_decoder@1.1.1: dependencies: @@ -19273,9 +20351,9 @@ snapshots: dependencies: component-emitter: 1.3.1 cookiejar: 2.1.4 - debug: 4.3.5 + debug: 4.3.7 fast-safe-stringify: 2.1.1 - form-data: 4.0.1 + form-data: 4.0.2 formidable: 2.1.2 methods: 1.1.2 mime: 2.6.0 @@ -19321,6 +20399,8 @@ snapshots: symbol-observable@4.0.0: {} + symbol-tree@3.2.4: {} + tapable@2.2.1: {} tar-fs@2.1.1: @@ -19402,6 +20482,12 @@ snapshots: glob: 7.2.3 minimatch: 3.1.2 + test-exclude@7.0.1: + dependencies: + '@istanbuljs/schema': 0.1.3 + glob: 10.4.5 + minimatch: 9.0.5 + text-decoder@1.1.1: dependencies: b4a: 1.6.6 @@ -19424,10 +20510,26 @@ snapshots: tinybench@2.8.0: {} + tinybench@2.9.0: {} + + tinyexec@0.3.2: {} + tinypool@0.8.4: {} + tinypool@1.0.2: {} + + tinyrainbow@2.0.0: {} + tinyspy@2.2.1: {} + tinyspy@3.0.2: {} + + tldts-core@6.1.76: {} + + tldts@6.1.76: + dependencies: + tldts-core: 6.1.76 + tmp@0.0.33: dependencies: os-tmpdir: 1.0.2 @@ -19453,6 +20555,10 @@ snapshots: '@tokenizer/token': 0.3.0 ieee754: 1.2.1 + tough-cookie@5.1.1: + dependencies: + tldts: 6.1.76 + tr46@0.0.3: {} tr46@3.0.0: @@ -19463,6 +20569,10 @@ snapshots: dependencies: punycode: 2.3.1 + tr46@5.0.0: + dependencies: + punycode: 2.3.1 + tree-kill@1.2.2: {} trim-lines@3.0.1: {} @@ -19483,7 +20593,7 @@ snapshots: ts-dedent@2.2.0: {} - ts-jest@29.2.2(@babel/core@7.24.9)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.24.9))(jest@29.7.0(@types/node@20.14.11)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@20.14.11)(typescript@5.5.3)))(typescript@5.5.3): + ts-jest@29.2.2(@babel/core@7.24.9)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.24.9))(jest@29.7.0(@types/node@20.14.11)(babel-plugin-macros@3.1.0))(typescript@5.5.3): dependencies: bs-logger: 0.2.6 ejs: 3.1.10 @@ -19530,6 +20640,10 @@ snapshots: v8-compile-cache-lib: 3.0.1 yn: 3.1.1 + tsconfck@3.1.5(typescript@5.5.3): + optionalDependencies: + typescript: 5.5.3 + tsconfig-paths-webpack-plugin@4.1.0: dependencies: chalk: 4.1.2 @@ -19559,6 +20673,8 @@ snapshots: tslib@2.8.0: {} + tslib@2.8.1: {} + tunnel-agent@0.6.0: dependencies: safe-buffer: 5.2.1 @@ -19596,7 +20712,7 @@ snapshots: dependencies: call-bind: 1.0.7 for-each: 0.3.3 - gopd: 1.0.1 + gopd: 1.2.0 has-proto: 1.0.3 is-typed-array: 1.1.13 @@ -19605,7 +20721,7 @@ snapshots: available-typed-arrays: 1.0.7 call-bind: 1.0.7 for-each: 0.3.3 - gopd: 1.0.1 + gopd: 1.2.0 has-proto: 1.0.3 is-typed-array: 1.1.13 @@ -19613,7 +20729,7 @@ snapshots: dependencies: call-bind: 1.0.7 for-each: 0.3.3 - gopd: 1.0.1 + gopd: 1.2.0 has-proto: 1.0.3 is-typed-array: 1.1.13 possible-typed-array-names: 1.0.0 @@ -19636,7 +20752,7 @@ snapshots: dependencies: call-bind: 1.0.7 has-bigints: 1.0.2 - has-symbols: 1.0.3 + has-symbols: 1.1.0 which-boxed-primitive: 1.0.2 unbzip2-stream@1.4.3: @@ -19758,6 +20874,12 @@ snapshots: escalade: 3.1.2 picocolors: 1.1.1 + update-browserslist-db@1.1.2(browserslist@4.24.4): + dependencies: + browserslist: 4.24.4 + escalade: 3.2.0 + picocolors: 1.1.1 + uri-js@4.4.1: dependencies: punycode: 2.3.1 @@ -19765,7 +20887,7 @@ snapshots: use-callback-ref@1.3.2(@types/react@18.3.1)(react@18.3.1): dependencies: react: 18.3.1 - tslib: 2.8.0 + tslib: 2.8.1 optionalDependencies: '@types/react': 18.3.1 @@ -19801,7 +20923,7 @@ snapshots: dependencies: detect-node-es: 1.1.0 react: 18.3.1 - tslib: 2.8.0 + tslib: 2.8.1 optionalDependencies: '@types/react': 18.3.1 @@ -19888,9 +21010,9 @@ snapshots: vite-node@1.6.0(@types/node@22.7.8)(sass@1.77.8)(terser@5.31.3): dependencies: cac: 6.7.14 - debug: 4.3.7 + debug: 4.4.0 pathe: 1.1.2 - picocolors: 1.0.1 + picocolors: 1.1.1 vite: 5.3.4(@types/node@22.7.8)(sass@1.77.8)(terser@5.31.3) transitivePeerDependencies: - '@types/node' @@ -19902,18 +21024,88 @@ snapshots: - supports-color - terser - vite@5.3.4(@types/node@22.7.8)(sass@1.77.8)(terser@5.31.3): + vite-node@3.0.5(@types/node@20.14.11)(sass@1.77.8)(terser@5.31.3): + dependencies: + cac: 6.7.14 + debug: 4.4.0 + es-module-lexer: 1.6.0 + pathe: 2.0.2 + vite: 5.3.4(@types/node@20.14.11)(sass@1.77.8)(terser@5.31.3) + transitivePeerDependencies: + - '@types/node' + - less + - lightningcss + - sass + - stylus + - sugarss + - supports-color + - terser + + vite-node@3.0.5(@types/node@22.7.8)(sass@1.77.8)(terser@5.31.3): + dependencies: + cac: 6.7.14 + debug: 4.4.0 + es-module-lexer: 1.6.0 + pathe: 2.0.2 + vite: 5.3.4(@types/node@22.7.8)(sass@1.77.8)(terser@5.31.3) + transitivePeerDependencies: + - '@types/node' + - less + - lightningcss + - sass + - stylus + - sugarss + - supports-color + - terser + + vite-tsconfig-paths@5.1.4(typescript@5.5.3)(vite@5.3.4(@types/node@22.7.8)(sass@1.77.8)(terser@5.31.3)): + dependencies: + debug: 4.3.7 + globrex: 0.1.2 + tsconfck: 3.1.5(typescript@5.5.3) + optionalDependencies: + vite: 5.3.4(@types/node@22.7.8)(sass@1.77.8)(terser@5.31.3) + transitivePeerDependencies: + - supports-color + - typescript + + vite@5.3.4(@types/node@20.14.11)(sass@1.77.8)(terser@5.31.3): dependencies: esbuild: 0.21.5 postcss: 8.4.39 rollup: 4.18.1 + optionalDependencies: + '@types/node': 20.14.11 + fsevents: 2.3.3 + sass: 1.77.8 + terser: 5.31.3 + + vite@5.3.4(@types/node@22.7.8)(sass@1.77.8)(terser@5.31.3): + dependencies: + esbuild: 0.21.5 + postcss: 8.5.3 + rollup: 4.18.1 optionalDependencies: '@types/node': 22.7.8 fsevents: 2.3.3 sass: 1.77.8 terser: 5.31.3 - vitest@1.6.0(@types/node@22.7.8)(sass@1.77.8)(terser@5.31.3): + vitest-mongodb@1.0.1(socks@2.8.3): + dependencies: + debug: 4.4.0 + mongodb-memory-server: 10.1.3(socks@2.8.3) + transitivePeerDependencies: + - '@aws-sdk/credential-providers' + - '@mongodb-js/zstd' + - gcp-metadata + - kerberos + - mongodb-client-encryption + - snappy + - socks + - supports-color + + vitest@1.6.0(@types/node@22.7.8)(jsdom@26.0.0)(sass@1.77.8)(terser@5.31.3): dependencies: '@vitest/expect': 1.6.0 '@vitest/runner': 1.6.0 @@ -19937,6 +21129,7 @@ snapshots: why-is-node-running: 2.3.0 optionalDependencies: '@types/node': 22.7.8 + jsdom: 26.0.0 transitivePeerDependencies: - less - lightningcss @@ -19946,6 +21139,78 @@ snapshots: - supports-color - terser + vitest@3.0.5(@types/debug@4.1.12)(@types/node@20.14.11)(jsdom@26.0.0)(sass@1.77.8)(terser@5.31.3): + dependencies: + '@vitest/expect': 3.0.5 + '@vitest/mocker': 3.0.5(vite@5.3.4(@types/node@22.7.8)(sass@1.77.8)(terser@5.31.3)) + '@vitest/pretty-format': 3.0.5 + '@vitest/runner': 3.0.5 + '@vitest/snapshot': 3.0.5 + '@vitest/spy': 3.0.5 + '@vitest/utils': 3.0.5 + chai: 5.1.2 + debug: 4.4.0 + expect-type: 1.1.0 + magic-string: 0.30.17 + pathe: 2.0.2 + std-env: 3.8.0 + tinybench: 2.9.0 + tinyexec: 0.3.2 + tinypool: 1.0.2 + tinyrainbow: 2.0.0 + vite: 5.3.4(@types/node@20.14.11)(sass@1.77.8)(terser@5.31.3) + vite-node: 3.0.5(@types/node@20.14.11)(sass@1.77.8)(terser@5.31.3) + why-is-node-running: 2.3.0 + optionalDependencies: + '@types/debug': 4.1.12 + '@types/node': 20.14.11 + jsdom: 26.0.0 + transitivePeerDependencies: + - less + - lightningcss + - msw + - sass + - stylus + - sugarss + - supports-color + - terser + + vitest@3.0.5(@types/debug@4.1.12)(@types/node@22.7.8)(jsdom@26.0.0)(sass@1.77.8)(terser@5.31.3): + dependencies: + '@vitest/expect': 3.0.5 + '@vitest/mocker': 3.0.5(vite@5.3.4(@types/node@22.7.8)(sass@1.77.8)(terser@5.31.3)) + '@vitest/pretty-format': 3.0.5 + '@vitest/runner': 3.0.5 + '@vitest/snapshot': 3.0.5 + '@vitest/spy': 3.0.5 + '@vitest/utils': 3.0.5 + chai: 5.1.2 + debug: 4.4.0 + expect-type: 1.1.0 + magic-string: 0.30.17 + pathe: 2.0.2 + std-env: 3.8.0 + tinybench: 2.9.0 + tinyexec: 0.3.2 + tinypool: 1.0.2 + tinyrainbow: 2.0.0 + vite: 5.3.4(@types/node@22.7.8)(sass@1.77.8)(terser@5.31.3) + vite-node: 3.0.5(@types/node@22.7.8)(sass@1.77.8)(terser@5.31.3) + why-is-node-running: 2.3.0 + optionalDependencies: + '@types/debug': 4.1.12 + '@types/node': 22.7.8 + jsdom: 26.0.0 + transitivePeerDependencies: + - less + - lightningcss + - msw + - sass + - stylus + - sugarss + - supports-color + - terser + void-elements@3.1.0: {} vue@3.4.32(typescript@5.5.3): @@ -19958,6 +21223,10 @@ snapshots: optionalDependencies: typescript: 5.5.3 + w3c-xmlserializer@5.0.0: + dependencies: + xml-name-validator: 5.0.0 + walker@1.0.8: dependencies: makeerror: 1.0.12 @@ -20016,6 +21285,12 @@ snapshots: - esbuild - uglify-js + whatwg-encoding@3.1.1: + dependencies: + iconv-lite: 0.6.3 + + whatwg-mimetype@4.0.0: {} + whatwg-url@11.0.0: dependencies: tr46: 3.0.0 @@ -20026,6 +21301,11 @@ snapshots: tr46: 4.1.1 webidl-conversions: 7.0.0 + whatwg-url@14.1.0: + dependencies: + tr46: 5.0.0 + webidl-conversions: 7.0.0 + whatwg-url@5.0.0: dependencies: tr46: 0.0.3 @@ -20068,7 +21348,7 @@ snapshots: available-typed-arrays: 1.0.7 call-bind: 1.0.7 for-each: 0.3.3 - gopd: 1.0.1 + gopd: 1.2.0 has-tostringtag: 1.0.2 which@2.0.2: @@ -20144,10 +21424,16 @@ snapshots: imurmurhash: 0.1.4 signal-exit: 3.0.7 + ws@8.18.0: {} + xlsx@https://cdn.sheetjs.com/xlsx-0.20.2/xlsx-0.20.2.tgz: {} + xml-name-validator@5.0.0: {} + xmlbuilder@10.1.1: {} + xmlchars@2.2.0: {} + xtend@4.0.2: {} y18n@4.0.3: {} @@ -20205,7 +21491,7 @@ snapshots: yjs@13.6.18: dependencies: - lib0: 0.2.98 + lib0: 0.2.99 yn@3.1.1: {} @@ -20213,7 +21499,7 @@ snapshots: yocto-queue@1.1.1: {} - zhlint@0.7.4(@types/node@22.7.8)(sass@1.77.8)(terser@5.31.3)(typescript@5.5.3): + zhlint@0.7.4(@types/node@22.7.8)(jsdom@26.0.0)(sass@1.77.8)(terser@5.31.3)(typescript@5.5.3): dependencies: chalk: 4.1.2 glob: 10.4.5 @@ -20222,7 +21508,7 @@ snapshots: remark-frontmatter: 1.3.3 remark-parse: 7.0.2 unified: 8.4.2 - vitest: 1.6.0(@types/node@22.7.8)(sass@1.77.8)(terser@5.31.3) + vitest: 1.6.0(@types/node@22.7.8)(jsdom@26.0.0)(sass@1.77.8)(terser@5.31.3) vue: 3.4.32(typescript@5.5.3) transitivePeerDependencies: - '@edge-runtime/vm' diff --git a/projects/README.md b/projects/README.md new file mode 100644 index 000000000000..f98add7a9cff --- /dev/null +++ b/projects/README.md @@ -0,0 +1,6 @@ +# 目录说明 + +该目录为 FastGPT 主项目。 + +- app 前端项目,用于展示和使用 FastGPT。 +- sandbox 沙盒项目,用于测试和开发。 diff --git a/projects/app/.env.template b/projects/app/.env.template index ff3b1f328256..3ecbefa7bddf 100644 --- a/projects/app/.env.template +++ b/projects/app/.env.template @@ -13,6 +13,10 @@ ROOT_KEY=fdafasd OPENAI_BASE_URL=https://api.openai.com/v1 # OpenAI API Key CHAT_API_KEY=sk-xxxx +# ai proxy api +AIPROXY_API_ENDPOINT=https://xxx.come +AIPROXY_API_TOKEN=xxxxx + # 强制将图片转成 base64 传递给模型 MULTIPLE_DATA_TO_BASE64=true diff --git a/projects/app/data/config.json b/projects/app/data/config.json index c5bf0c5e1f7e..0b9e7a598c52 100644 --- a/projects/app/data/config.json +++ b/projects/app/data/config.json @@ -4,9 +4,16 @@ "lafEnv": "https://laf.dev" // laf环境。 https://laf.run (杭州阿里云) ,或者私有化的laf环境。如果使用 Laf openapi 功能,需要最新版的 laf 。 }, "systemEnv": { - "vectorMaxProcess": 15, // 向量处理线程数量 - "qaMaxProcess": 15, // 问答拆分线程数量 + "vectorMaxProcess": 10, // 向量处理线程数量 + "qaMaxProcess": 10, // 问答拆分线程数量 + "vlmMaxProcess": 10, // 图片理解模型最大处理进程 "tokenWorkers": 30, // Token 计算线程保持数,会持续占用内存,不能设置太大。 - "pgHNSWEfSearch": 100 // 向量搜索参数。越大,搜索越精确,但是速度越慢。设置为100,有99%+精度。 + "pgHNSWEfSearch": 100, // 向量搜索参数。越大,搜索越精确,但是速度越慢。设置为100,有99%+精度。 + "customPdfParse": { + "url": "", // 自定义 PDF 解析服务地址 + "key": "", // 自定义 PDF 解析服务密钥 + "doc2xKey": "", // doc2x 服务密钥 + "price": 0 // PDF 解析服务价格 + } } } diff --git a/projects/app/jest.config.js b/projects/app/jest.config.js deleted file mode 100644 index 73aa436ade82..000000000000 --- a/projects/app/jest.config.js +++ /dev/null @@ -1,179 +0,0 @@ -/** - * For a detailed explanation regarding each configuration property, visit: - * https://jestjs.io/docs/configuration - */ -const esModules = ['nanoid'].join('|'); - -/** @type {import('jest').Config} */ -const config = { - // All imported modules in your tests should be mocked automatically - // automock: false, - - // Stop running tests after `n` failures - // bail: 0, - - // The directory where Jest should store its cached dependency information - // cacheDirectory: "/tmp/jest_rs", - - // Automatically clear mock calls, instances, contexts and results before every test - // clearMocks: false, - - // Indicates whether the coverage information should be collected while executing the test - collectCoverage: true, - - // An array of glob patterns indicating a set of files for which coverage information should be collected - // collectCoverageFrom: undefined, - - // The directory where Jest should output its coverage files - coverageDirectory: './tmp/coverage', - - // An array of regexp pattern strings used to skip coverage collection - coveragePathIgnorePatterns: ['/node_modules/', '/__mocks__/', '/src/test/'], - - // Indicates which provider should be used to instrument code for coverage - // coverageProvider: "babel", - - // A list of reporter names that Jest uses when writing coverage reports - coverageReporters: ['json', 'text', 'lcov', 'clover'], - - // An object that configures minimum threshold enforcement for coverage results - // coverageThreshold: undefined, - - // A path to a custom dependency extractor - // dependencyExtractor: undefined, - - // Make calling deprecated APIs throw helpful error messages - // errorOnDeprecated: false, - - // The default configuration for fake timers - // fakeTimers: { - // "enableGlobally": false - // }, - - // Force coverage collection from ignored files using an array of glob patterns - // forceCoverageMatch: [], - - // A path to a module which exports an async function that is triggered once before all test suites - // globalSetup: undefined, - - // A path to a module which exports an async function that is triggered once after all test suites - // globalTeardown: undefined, - - // A set of global variables that need to be available in all test environments - // globals: {}, - - // The maximum amount of workers used to run your tests. Can be specified as % or a number. E.g. maxWorkers: 10% will use 10% of your CPU amount + 1 as the maximum worker number. maxWorkers: 2 will use a maximum of 2 workers. - // maxWorkers: "50%", - - // An array of directory names to be searched recursively up from the requiring module's location - moduleDirectories: ['node_modules', 'src'], - - // An array of file extensions your modules use - // moduleFileExtensions: ['js', 'mjs', 'cjs', 'jsx', 'ts', 'tsx', 'json', 'node'], - - // A map from regular expressions to module names or to arrays of module names that allow to stub out resources with a single module - moduleNameMapper: { - '@/(.*)': '/src/$1', - '^nanoid(/(.*)|$)': 'nanoid$1' - }, - - // An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader - // modulePathIgnorePatterns: [], - - // Activates notifications for test results - // notify: false, - - // An enum that specifies notification mode. Requires { notify: true } - // notifyMode: "failure-change", - - // A preset that is used as a base for Jest's configuration - preset: 'ts-jest', - - // Run tests from one or more projects - // projects: undefined, - - // Use this configuration option to add custom reporters to Jest - // reporters: undefined, - - // Automatically reset mock state before every test - // resetMocks: false, - - // Reset the module registry before running each individual test - // resetModules: false, - - // A path to a custom resolver - // resolver: undefined, - - // Automatically restore mock state and implementation before every test - // restoreMocks: false, - - // The root directory that Jest should scan for tests and modules within - // rootDir: undefined, - - // A list of paths to directories that Jest should use to search for files in - // roots: [ - // "" - // ], - - // Allows you to use a custom runner instead of Jest's default test runner - // runner: "jest-runner", - - // The paths to modules that run some code to configure or set up the testing environment before each test - // setupFiles: [], - - // A list of paths to modules that run some code to configure or set up the testing framework before each test - // setupFilesAfterEnv: [], - - // The number of seconds after which a test is considered as slow and reported as such in the results. - // slowTestThreshold: 5, - - // A list of paths to snapshot serializer modules Jest should use for snapshot testing - // snapshotSerializers: [], - - // The test environment that will be used for testing - testEnvironment: 'node', - - // Options that will be passed to the testEnvironment - // testEnvironmentOptions: {}, - - // Adds a location field to test results - // testLocationInResults: false, - - // The glob patterns Jest uses to detect test files - // testMatch: [ - // "**/__tests__/**/*.[jt]s?(x)", - // "**/?(*.)+(spec|test).[tj]s?(x)" - // ], - - // An array of regexp pattern strings that are matched against all test paths, matched tests are skipped - // testPathIgnorePatterns: ['/node_modules/'], - - // The regexp pattern or array of patterns that Jest uses to detect test files - // testRegex: [], - - // This option allows the use of a custom results processor - // testResultsProcessor: undefined, - - // This option allows use of a custom test runner - // testRunner: "jest-circus/runner", - - // A map from regular expressions to paths to transformers - transform: {}, - - // An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation - transformIgnorePatterns: [`/node_modules/(?!${esModules})`] - - // An array of regexp pattern strings that are matched against all modules before the module loader will automatically return a mock for them - // unmockedModulePathPatterns: undefined, - - // Indicates whether each individual test should be reported during the run - // verbose: undefined, - - // An array of regexp patterns that are matched against all source file paths before re-running tests in watch mode - // watchPathIgnorePatterns: [], - - // Whether to use watchman for file crawling - // watchman: true, -}; - -module.exports = config; diff --git a/projects/app/package.json b/projects/app/package.json index 81c9637677f7..354ce0a2e5fe 100644 --- a/projects/app/package.json +++ b/projects/app/package.json @@ -1,13 +1,12 @@ { "name": "app", - "version": "4.8.22", + "version": "4.9.0", "private": false, "scripts": { "dev": "next dev", "build": "next build", "start": "next start", - "lint": "next lint", - "test": "jest" + "lint": "next lint" }, "dependencies": { "@chakra-ui/anatomy": "2.2.1", @@ -26,10 +25,9 @@ "@fortaine/fetch-event-source": "^3.0.6", "@node-rs/jieba": "1.10.0", "@tanstack/react-query": "^4.24.10", - "@types/jest": "^29.5.2", "@types/nprogress": "^0.2.0", "ahooks": "^3.7.11", - "axios": "^1.5.1", + "axios": "^1.8.2", "date-fns": "2.30.0", "dayjs": "^1.11.7", "echarts": "5.4.1", @@ -39,7 +37,6 @@ "hyperdown": "^2.4.29", "i18next": "23.11.5", "immer": "^9.0.19", - "jest": "^29.5.0", "js-yaml": "^4.1.0", "json5": "^2.2.3", "jsondiffpatch": "^0.6.0", @@ -47,7 +44,7 @@ "lodash": "^4.17.21", "mermaid": "^10.2.3", "nanoid": "^4.0.1", - "next": "14.2.5", + "next": "14.2.21", "next-i18next": "15.3.0", "nextjs-node-loader": "^1.1.5", "nprogress": "^0.2.0", @@ -69,13 +66,10 @@ "remark-math": "^6.0.0", "request-ip": "^3.3.0", "sass": "^1.58.3", - "ts-jest": "^29.1.0", "use-context-selector": "^1.4.4", "zustand": "^4.3.5" }, "devDependencies": { - "@faker-js/faker": "^9.0.3", - "@shelf/jest-mongodb": "^4.3.2", "@svgr/webpack": "^6.5.1", "@types/formidable": "^2.0.5", "@types/js-yaml": "^4.0.9", @@ -89,8 +83,6 @@ "@types/request-ip": "^0.0.37", "eslint": "8.56.0", "eslint-config-next": "14.2.3", - "mockingoose": "^2.16.2", - "mongodb-memory-server": "^10.0.0", "nextjs-node-loader": "^1.1.5", "typescript": "^5.1.3" } diff --git a/projects/app/public/docs/versionIntro.md b/projects/app/public/docs/versionIntro.md index c7b1571fcd67..c7aeb68e7d85 100644 --- a/projects/app/public/docs/versionIntro.md +++ b/projects/app/public/docs/versionIntro.md @@ -1,13 +1,21 @@ -### FastGPT V4.8.20 更新说明 - -1. 新增 - 使用记录导出和仪表盘。 -2. 新增 - DeepSeek resoner 模型支持输出思考过程。 -3. 新增 - markdown 语法扩展,支持音视频(代码块 audio 和 video)。 -4. 新增 - 飞书/语雀知识库。 -5. 新增 - 工作流知识库检索支持按知识库权限进行过滤。 -6. 新增 - 流程等待插件,可以等待 n 毫秒后继续执行流程。 -7. 新增 - 飞书机器人接入,支持配置私有化飞书地址。 -8. 新增 - 支持通过 JSON 配置直接创建应用。 -9. 新增 - 支持通过 CURL 脚本快速创建 HTTP 插件。 -10. 新增 - 支持部门架构权限模式。 +### FastGPT V4.9.0 更新说明 + +#### 弃用 & 兼容 + +1. 弃用 - 之前私有化部署的自定义文件解析方案,请同步更新到最新的配置方案。[点击查看 PDF 增强解析配置](/docs/development/configuration/#使用-doc2x-解析-pdf-文件) +2. 弃用 - 弃用旧版本地文件上传 API:/api/core/dataset/collection/create/file(以前仅商业版可用的 API,该接口已放切换成:/api/core/dataset/collection/create/localFile) +3. 停止维护,即将弃用 - 外部文件库相关 API,可通过 API 文件库替代。 +4. API更新 - 上传文件至知识库、创建连接集合、API 文件库、推送分块数据等带有 `trainingType` 字段的接口,`trainingType`字段未来仅支持`chunk`和`QA`两种模式。增强索引模式将设置单独字段:`autoIndexes`,目前仍有适配旧版`trainingType=auto`代码,但请尽快变更成新接口类型。具体可见:[知识库 OpenAPI 文档](/docs/development/openapi/dataset.md) + +#### 功能更新 + +1. 新增 - PDF 增强解析,可以识别图片、公式、扫描件,并将内容转化成 Markdown 格式。 +2. 新增 - 支持对文档中的图片链接,进行图片索引,提高图片内容的检索精度。 +3. 新增 - 语义检索增加迭代搜索,减少漏检。 +4. 优化 - 知识库数据不再限制索引数量,可无限自定义。同时可自动更新输入文本的索引,不影响自定义索引。 +5. 优化 - Markdown 解析,增加链接后中文标点符号检测,增加空格。 +6. 优化 - Prompt 模式工具调用,支持思考模型。同时优化其格式检测,减少空输出的概率。 +7. 优化 - 优化文件读取代码,极大提高大文件读取速度。50M PDF 读取时间提高 3 倍。 +8. 优化 - HTTP Body 适配,增加对字符串对象的适配。 +9. 修复 - 批量运行时,全局变量未进一步传递到下一次运行中,导致最终变量更新错误。 diff --git a/projects/app/src/components/Layout/index.tsx b/projects/app/src/components/Layout/index.tsx index 9d53d3da02e0..2ece77c2791a 100644 --- a/projects/app/src/components/Layout/index.tsx +++ b/projects/app/src/components/Layout/index.tsx @@ -51,13 +51,13 @@ export const navbarWidth = '64px'; const Layout = ({ children }: { children: JSX.Element }) => { const router = useRouter(); + const { toast } = useToast(); const { t } = useTranslation(); const { Loading } = useLoading(); const { loading, feConfigs, notSufficientModalType, llmModelList, embeddingModelList } = useSystemStore(); const { isPc } = useSystem(); - const { userInfo, teamPlanStatus, isUpdateNotification, setIsUpdateNotification } = - useUserStore(); + const { userInfo, isUpdateNotification, setIsUpdateNotification } = useUserStore(); const { setUserDefaultLng } = useI18nLng(); const isChatPage = useMemo( @@ -87,7 +87,6 @@ const Layout = ({ children }: { children: JSX.Element }) => { }); // Check model invalid - const { toast } = useToast(); useDebounceEffect( () => { if (userInfo?.username === 'root') { diff --git a/projects/app/src/components/Markdown/utils.ts b/projects/app/src/components/Markdown/utils.ts index d20f6084873b..6b4d1e5c56d7 100644 --- a/projects/app/src/components/Markdown/utils.ts +++ b/projects/app/src/components/Markdown/utils.ts @@ -14,32 +14,30 @@ export enum CodeClassNameEnum { export const mdTextFormat = (text: string) => { // NextChat function - Format latex to $$ - const escapeBrackets = (text: string) => { - const pattern = /(```[\s\S]*?```|`.*?`)|\\\[([\s\S]*?[^\\])\\\]|\\\((.*?)\\\)/g; - return text.replace(pattern, (match, codeBlock, squareBracket, roundBracket) => { - if (codeBlock) { - return codeBlock; - } else if (squareBracket) { - return `$$${squareBracket}$$`; - } else if (roundBracket) { - return `$${roundBracket}$`; - } - return match; - }); - }; + const pattern = /(```[\s\S]*?```|`.*?`)|\\\[([\s\S]*?[^\\])\\\]|\\\((.*?)\\\)/g; + text = text.replace(pattern, (match, codeBlock, squareBracket, roundBracket) => { + if (codeBlock) { + return codeBlock; + } else if (squareBracket) { + return `$$${squareBracket}$$`; + } else if (roundBracket) { + return `$${roundBracket}$`; + } + return match; + }); + // 处理 [quote:id] 格式引用,将 [quote:675934a198f46329dfc6d05a] 转换为 [675934a198f46329dfc6d05a](QUOTE) - const formatQuote = (text: string) => { - return ( - text - // .replace( - // /([\u4e00-\u9fa5\u3000-\u303f])([a-zA-Z0-9])|([a-zA-Z0-9])([\u4e00-\u9fa5\u3000-\u303f])/g, - // '$1$3 $2$4' - // ) - // 处理 [quote:id] 格式引用,将 [quote:675934a198f46329dfc6d05a] 转换为 [675934a198f46329dfc6d05a](QUOTE) - .replace(/\[quote:?\s*([a-f0-9]{24})\](?!\()/gi, '[$1](QUOTE)') - .replace(/\[([a-f0-9]{24})\](?!\()/g, '[$1](QUOTE)') - ); - }; + text = text + // .replace( + // /([\u4e00-\u9fa5\u3000-\u303f])([a-zA-Z0-9])|([a-zA-Z0-9])([\u4e00-\u9fa5\u3000-\u303f])/g, + // '$1$3 $2$4' + // ) + // 处理 [quote:id] 格式引用,将 [quote:675934a198f46329dfc6d05a] 转换为 [675934a198f46329dfc6d05a](QUOTE) + .replace(/\[quote:?\s*([a-f0-9]{24})\](?!\()/gi, '[$1](QUOTE)') + .replace(/\[([a-f0-9]{24})\](?!\()/g, '[$1](QUOTE)'); + + // 处理链接后的中文标点符号,增加空格 + text = text.replace(/(https?:\/\/[^\s,。!?;:、]+)([,。!?;:、])/g, '$1 $2'); - return formatQuote(escapeBrackets(text)); + return text; }; diff --git a/projects/app/src/components/Select/AIModelSelector.tsx b/projects/app/src/components/Select/AIModelSelector.tsx index 2a0881e9ff33..3a760e44a670 100644 --- a/projects/app/src/components/Select/AIModelSelector.tsx +++ b/projects/app/src/components/Select/AIModelSelector.tsx @@ -35,19 +35,18 @@ const OneRowSelector = ({ list, onchange, disableTip, ...props }: Props) => { return props.size ? size[props.size] : size['md']; }, [props.size]); - const avatarList = useMemo( - () => - list.map((item) => { - const modelData = getModelFromList( - [ - ...llmModelList, - ...embeddingModelList, - ...ttsModelList, - ...sttModelList, - ...reRankModelList - ], - item.value - ); + const avatarList = useMemo(() => { + const allModels = [ + ...llmModelList, + ...embeddingModelList, + ...ttsModelList, + ...sttModelList, + ...reRankModelList + ]; + return list + .map((item) => { + const modelData = getModelFromList(allModels, item.value)!; + if (!modelData) return; return { value: item.value, @@ -64,17 +63,20 @@ const OneRowSelector = ({ list, onchange, disableTip, ...props }: Props) => { ) }; - }), - [ - list, - llmModelList, - embeddingModelList, - ttsModelList, - sttModelList, - reRankModelList, - avatarSize - ] - ); + }) + .filter(Boolean) as { + value: any; + label: React.JSX.Element; + }[]; + }, [ + list, + llmModelList, + embeddingModelList, + ttsModelList, + sttModelList, + reRankModelList, + avatarSize + ]); return ( { className="nowheel" isDisabled={!!disableTip} list={avatarList} + placeholder={t('common:not_model_config')} h={'40px'} {...props} onchange={(e) => { @@ -107,19 +110,21 @@ const OneRowSelector = ({ list, onchange, disableTip, ...props }: Props) => { ); }; -const MultipleRowSelector = ({ list, onchange, disableTip, ...props }: Props) => { +const MultipleRowSelector = ({ list, onchange, disableTip, placeholder, ...props }: Props) => { const { t } = useTranslation(); const { llmModelList, embeddingModelList, ttsModelList, sttModelList, reRankModelList } = useSystemStore(); const modelList = useMemo(() => { - return [ + const allModels = [ ...llmModelList, ...embeddingModelList, ...ttsModelList, ...sttModelList, ...reRankModelList ]; - }, [llmModelList, embeddingModelList, ttsModelList, sttModelList, reRankModelList]); + + return list.map((item) => getModelFromList(allModels, item.value)!).filter(Boolean); + }, [llmModelList, embeddingModelList, ttsModelList, sttModelList, reRankModelList, list]); const [value, setValue] = useState([]); @@ -157,6 +162,7 @@ const MultipleRowSelector = ({ list, onchange, disableTip, ...props }: Props) => for (const item of list) { const modelData = getModelFromList(modelList, item.value); + if (!modelData) continue; const provider = renderList.find((item) => item.value === (modelData?.provider || 'Other')) ?? renderList[renderList.length - 1]; @@ -168,7 +174,7 @@ const MultipleRowSelector = ({ list, onchange, disableTip, ...props }: Props) => } return renderList.filter((item) => item.children.length > 0); - }, [avatarSize, list, modelList]); + }, [avatarSize, list, modelList, t]); const onSelect = useCallback( (e: string[]) => { @@ -178,8 +184,11 @@ const MultipleRowSelector = ({ list, onchange, disableTip, ...props }: Props) => ); const SelectedModel = useMemo(() => { + if (!props.value) return <>{t('common:not_model_config')}; const modelData = getModelFromList(modelList, props.value); + if (!modelData) return <>{t('common:not_model_config')}; + setValue([modelData.provider, props.value]); return ( @@ -194,7 +203,7 @@ const MultipleRowSelector = ({ list, onchange, disableTip, ...props }: Props) => {modelData?.name} ); - }, [modelList, props.value, avatarSize]); + }, [modelList, props.value, t, avatarSize]); return ( list={selectorList} onSelect={onSelect} value={value} + placeholder={placeholder} rowMinWidth="160px" ButtonProps={{ isDisabled: !!disableTip, diff --git a/projects/app/src/components/core/app/FileSelect.tsx b/projects/app/src/components/core/app/FileSelect.tsx index 7f7361d9c32b..f1e3879bcf9b 100644 --- a/projects/app/src/components/core/app/FileSelect.tsx +++ b/projects/app/src/components/core/app/FileSelect.tsx @@ -9,7 +9,8 @@ import { HStack, Switch, ModalFooter, - BoxProps + BoxProps, + Checkbox } from '@chakra-ui/react'; import React, { useMemo } from 'react'; import { useTranslation } from 'next-i18next'; @@ -22,6 +23,8 @@ import FormLabel from '@fastgpt/web/components/common/MyBox/FormLabel'; import { useMount } from 'ahooks'; import { useSystemStore } from '@/web/common/system/useSystemStore'; import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip'; +import MyTag from '@fastgpt/web/components/common/Tag/index'; +import MyDivider from '@fastgpt/web/components/common/MyDivider'; const FileSelect = ({ forbidVision = false, @@ -95,6 +98,42 @@ const FileSelect = ({ }} /> + {value.canSelectFile && feConfigs.showCustomPdfParse && ( + <> + + { + onChange({ + ...value, + customPdfParse: e.target.checked + }); + }} + > + {t('app:pdf_enhance_parse')} + + + {feConfigs?.show_pay && ( + + {t('app:pdf_enhance_parse_price', { + price: feConfigs.customPdfParsePrice || 0 + })} + + )} + + + + )} {t('app:image_upload')} {forbidVision ? ( diff --git a/projects/app/src/components/core/app/TTSSelect.tsx b/projects/app/src/components/core/app/TTSSelect.tsx index 8b6fecd023c6..3e7a93c46340 100644 --- a/projects/app/src/components/core/app/TTSSelect.tsx +++ b/projects/app/src/components/core/app/TTSSelect.tsx @@ -42,7 +42,7 @@ const TTSSelect = ({ label: ( - {t(providerData.name)} + {t(model.name as any)} ), value: model.model, @@ -75,7 +75,7 @@ const TTSSelect = ({ {voice ? ( {provider.label} - - + / {voice.label} ) : ( @@ -83,7 +83,7 @@ const TTSSelect = ({ )} ); - }, [formatValue, selectorList, t]); + }, [formatValue, selectorList]); const { playAudioByText, cancelAudio, audioLoading, audioPlaying } = useAudioPlay({ appId, diff --git a/projects/app/src/global/aiproxy/constants.ts b/projects/app/src/global/aiproxy/constants.ts new file mode 100644 index 000000000000..5fb0d35c0ec6 --- /dev/null +++ b/projects/app/src/global/aiproxy/constants.ts @@ -0,0 +1,128 @@ +import { ModelProviderIdType } from '@fastgpt/global/core/ai/provider'; +import { ChannelInfoType } from './type'; +import { i18nT } from '@fastgpt/web/i18n/utils'; + +export enum ChannelStatusEnum { + ChannelStatusUnknown = 0, + ChannelStatusEnabled = 1, + ChannelStatusDisabled = 2, + ChannelStatusAutoDisabled = 3 +} +export const ChannelStautsMap = { + [ChannelStatusEnum.ChannelStatusUnknown]: { + label: i18nT('account_model:channel_status_unknown'), + colorSchema: 'gray' + }, + [ChannelStatusEnum.ChannelStatusEnabled]: { + label: i18nT('account_model:channel_status_enabled'), + colorSchema: 'green' + }, + [ChannelStatusEnum.ChannelStatusDisabled]: { + label: i18nT('account_model:channel_status_disabled'), + colorSchema: 'red' + }, + [ChannelStatusEnum.ChannelStatusAutoDisabled]: { + label: i18nT('account_model:channel_status_auto_disabled'), + colorSchema: 'gray' + } +}; + +export const defaultChannel: ChannelInfoType = { + id: 0, + status: ChannelStatusEnum.ChannelStatusEnabled, + type: 1, + created_at: 0, + models: [], + model_mapping: {}, + key: '', + name: '', + base_url: '', + priority: 0 +}; + +export const aiproxyIdMap: Record = { + 1: { + label: 'OpenAI', + provider: 'OpenAI' + }, + 3: { + label: i18nT('account_model:azure'), + provider: 'OpenAI' + }, + 14: { + label: 'Anthropic', + provider: 'Claude' + }, + 12: { + label: 'Google Gemini(OpenAI)', + provider: 'Gemini' + }, + 24: { + label: 'Google Gemini', + provider: 'Gemini' + }, + 28: { + label: 'Mistral AI', + provider: 'MistralAI' + }, + 29: { + label: 'Groq', + provider: 'Groq' + }, + 17: { + label: '阿里云', + provider: 'Qwen' + }, + 40: { + label: '豆包', + provider: 'Doubao' + }, + 36: { + label: 'DeepSeek AI', + provider: 'DeepSeek' + }, + 13: { + label: '百度智能云 V2', + provider: 'Ernie' + }, + 15: { + label: '百度智能云', + provider: 'Ernie' + }, + 16: { + label: '智谱 AI', + provider: 'ChatGLM' + }, + 18: { + label: '讯飞星火', + provider: 'SparkDesk' + }, + 25: { + label: '月之暗面', + provider: 'Moonshot' + }, + 26: { + label: '百川智能', + provider: 'Baichuan' + }, + 27: { + label: 'MiniMax', + provider: 'MiniMax' + }, + 31: { + label: '零一万物', + provider: 'Yi' + }, + 32: { + label: '阶跃星辰', + provider: 'StepFun' + }, + 43: { + label: 'SiliconFlow', + provider: 'Siliconflow' + }, + 30: { + label: 'Ollama', + provider: 'Ollama' + } +}; diff --git a/projects/app/src/global/aiproxy/type.d.ts b/projects/app/src/global/aiproxy/type.d.ts new file mode 100644 index 000000000000..8238f2093c01 --- /dev/null +++ b/projects/app/src/global/aiproxy/type.d.ts @@ -0,0 +1,47 @@ +import { ChannelStatusEnum } from './constants'; + +export type ChannelInfoType = { + model_mapping: Record; + key: string; + name: string; + base_url: string; + models: any[]; + id: number; + status: ChannelStatusEnum; + type: number; + created_at: number; + priority: number; +}; + +// Channel api +export type ChannelListQueryType = { + page: number; + perPage: number; +}; +export type ChannelListResponseType = ChannelInfoType[]; + +export type CreateChannelProps = { + type: number; + model_mapping: Record; + key?: string; + name: string; + base_url: string; + models: string[]; +}; + +// Log +export type ChannelLogListItemType = { + token_name: string; + model: string; + request_id: string; + id: number; + channel: number; + mode: number; + created_at: number; + request_at: number; + code: number; + prompt_tokens: number; + completion_tokens: number; + endpoint: string; + content?: string; +}; diff --git a/projects/app/src/global/core/dataset/api.d.ts b/projects/app/src/global/core/dataset/api.d.ts index 5716b3503835..fc09332c63f8 100644 --- a/projects/app/src/global/core/dataset/api.d.ts +++ b/projects/app/src/global/core/dataset/api.d.ts @@ -26,6 +26,7 @@ export type CreateDatasetParams = { avatar: string; vectorModel?: string; agentModel?: string; + vlmModel?: string; apiServer?: APIFileServer; feishuServer?: FeishuServer; yuqueServer?: YuqueServer; diff --git a/projects/app/src/pageComponents/account/model/AddModelBox.tsx b/projects/app/src/pageComponents/account/model/AddModelBox.tsx new file mode 100644 index 000000000000..3ed5f08257d9 --- /dev/null +++ b/projects/app/src/pageComponents/account/model/AddModelBox.tsx @@ -0,0 +1,759 @@ +import { + Box, + Flex, + HStack, + Table, + TableContainer, + Tbody, + Td, + Th, + Thead, + Tr, + Switch, + ModalBody, + Input, + ModalFooter, + Button, + ButtonProps +} from '@chakra-ui/react'; +import { useTranslation } from 'next-i18next'; +import React, { useMemo, useRef, useState } from 'react'; +import { + ModelProviderList, + ModelProviderIdType, + getModelProvider +} from '@fastgpt/global/core/ai/provider'; +import MySelect from '@fastgpt/web/components/common/MySelect'; +import { ModelTypeEnum } from '@fastgpt/global/core/ai/model'; +import Avatar from '@fastgpt/web/components/common/Avatar'; +import { useRequest2 } from '@fastgpt/web/hooks/useRequest'; +import { getSystemModelDefaultConfig, putSystemModel } from '@/web/core/ai/config'; +import { SystemModelItemType } from '@fastgpt/service/core/ai/type'; +import { useForm } from 'react-hook-form'; +import MyNumberInput from '@fastgpt/web/components/common/Input/NumberInput'; +import MyTextarea from '@/components/common/Textarea/MyTextarea'; +import JsonEditor from '@fastgpt/web/components/common/Textarea/JsonEditor'; +import MyMenu from '@fastgpt/web/components/common/MyMenu'; +import { useSystemStore } from '@/web/common/system/useSystemStore'; +import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip'; +import { Prompt_CQJson, Prompt_ExtractJson } from '@fastgpt/global/core/ai/prompt/agent'; +import MyModal from '@fastgpt/web/components/common/MyModal'; +import FormLabel from '@fastgpt/web/components/common/MyBox/FormLabel'; + +export const AddModelButton = ({ + onCreate, + ...props +}: { onCreate: (type: ModelTypeEnum) => void } & ButtonProps) => { + const { t } = useTranslation(); + + return ( + {t('account:create_model')}} + menuList={[ + { + children: [ + { + label: t('common:model.type.chat'), + onClick: () => onCreate(ModelTypeEnum.llm) + }, + { + label: t('common:model.type.embedding'), + onClick: () => onCreate(ModelTypeEnum.embedding) + }, + { + label: t('common:model.type.tts'), + onClick: () => onCreate(ModelTypeEnum.tts) + }, + { + label: t('common:model.type.stt'), + onClick: () => onCreate(ModelTypeEnum.stt) + }, + { + label: t('common:model.type.reRank'), + onClick: () => onCreate(ModelTypeEnum.rerank) + } + ] + } + ]} + /> + ); +}; + +const InputStyles = { + maxW: '300px', + bg: 'myGray.50', + w: '100%', + rows: 3 +}; +export const ModelEditModal = ({ + modelData, + onSuccess, + onClose +}: { + modelData: SystemModelItemType; + onSuccess: () => void; + onClose: () => void; +}) => { + const { t } = useTranslation(); + const { feConfigs } = useSystemStore(); + + const { register, getValues, setValue, handleSubmit, watch, reset } = + useForm({ + defaultValues: modelData + }); + + const isCustom = !!modelData.isCustom; + const isLLMModel = modelData?.type === ModelTypeEnum.llm; + const isEmbeddingModel = modelData?.type === ModelTypeEnum.embedding; + const isTTSModel = modelData?.type === ModelTypeEnum.tts; + const isSTTModel = modelData?.type === ModelTypeEnum.stt; + const isRerankModel = modelData?.type === ModelTypeEnum.rerank; + + const provider = watch('provider'); + const providerData = useMemo(() => getModelProvider(provider), [provider]); + + const providerList = useRef<{ label: any; value: ModelProviderIdType }[]>( + ModelProviderList.map((item) => ({ + label: ( + + + {t(item.name as any)} + + ), + value: item.id + })) + ); + + const priceUnit = useMemo(() => { + if (isLLMModel || isEmbeddingModel) return '/ 1k Tokens'; + if (isTTSModel) return `/ 1k ${t('common:unit.character')}`; + if (isSTTModel) return `/ 60 ${t('common:unit.seconds')}`; + return ''; + }, [isLLMModel, isEmbeddingModel, isTTSModel, t, isSTTModel]); + + const { runAsync: updateModel, loading: updatingModel } = useRequest2( + async (data: SystemModelItemType) => { + for (const key in data) { + // @ts-ignore + const val = data[key]; + if (val === null || val === undefined || Number.isNaN(val)) { + // @ts-ignore + data[key] = ''; + } + } + return putSystemModel({ + model: data.model, + metadata: data + }).then(onSuccess); + }, + { + onSuccess: () => { + onClose(); + }, + successToast: t('common:common.Success') + } + ); + + const [key, setKey] = useState(0); + const { runAsync: loadDefaultConfig, loading: loadingDefaultConfig } = useRequest2( + getSystemModelDefaultConfig, + { + onSuccess(res) { + reset({ + ...getValues(), + ...res + }); + setTimeout(() => { + setKey((prev) => prev + 1); + }, 0); + } + } + ); + + return ( + + + + + + + + + + + + + + + + + + + + + + + + + {priceUnit && feConfigs?.isPlus && ( + <> + + + + + {isLLMModel && ( + <> + + + + + + + + + + )} + + )} + {isLLMModel && ( + <> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + )} + {isEmbeddingModel && ( + <> + + + + + + + + + + + + + + + + + + )} + {isTTSModel && ( + <> + + + + + + )} + + + + + + + + + +
{t('account:model.param_name')}
+ + {t('account:model.model_id')} + + + + {isCustom ? ( + + ) : ( + modelData?.model + )} +
{t('common:model.provider')} + setValue('provider', value)} + list={providerList.current} + {...InputStyles} + /> +
+ + {t('account:model.alias')} + + + + +
+ + {t('account:model.charsPointsPrice')} + + + + + + + {priceUnit} + + +
+ + {t('account:model.input_price')} + + + + + + + {priceUnit} + + +
+ + {t('account:model.output_price')} + + + + + + + {priceUnit} + + +
+ + {t('common:core.ai.Max context')} + + + + + +
+ + {t('account:model.max_quote')} + + + + + +
+ + {t('common:core.chat.response.module maxToken')} + + + + + + +
+ + {t('account:model.max_temperature')} + + + + + + +
+ + {t('account:model.show_top_p')} + + + + + +
+ + {t('account:model.show_stop_sign')} + + + + + +
{t('account:model.response_format')} + { + if (!e) { + setValue('responseFormatList', []); + return; + } + try { + setValue('responseFormatList', JSON.parse(e)); + } catch (error) { + console.error(error); + } + }} + {...InputStyles} + /> +
+ + {t('account:model.normalization')} + + + + + + +
+ + {t('account:model.default_token')} + + + + + + +
{t('common:core.ai.Max context')} + + + +
+ + {t('account:model.defaultConfig')} + + + + + { + if (!e) { + setValue('defaultConfig', {}); + return; + } + try { + setValue('defaultConfig', JSON.parse(e)); + } catch (error) { + console.error(error); + } + }} + {...InputStyles} + /> + +
+ + {t('account:model.voices')} + + + + + { + try { + setValue('voices', JSON.parse(e)); + } catch (error) { + console.error(error); + } + }} + {...InputStyles} + /> + +
+ + {t('account:model.request_url')} + + + + +
+ + {t('account:model.request_auth')} + + + + +
+
+ {isLLMModel && ( + + + + + + + + + + + + + + + + + + + + + + + + + + {feConfigs?.isPlus && ( + + + + + )} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
{t('account:model.param_name')}
+ + {t('account:model.tool_choice')} + + + + + + +
+ + {t('account:model.function_call')} + + + + + + +
+ + {t('account:model.vision')} + + + + + + +
+ + {t('account:model.reasoning')} + + + + + + +
+ + {t('account:model.censor')} + + + + + + +
{t('account:model.dataset_process')} + + + +
{t('account:model.used_in_classify')} + + + +
{t('account:model.used_in_extract_fields')} + + + +
{t('account:model.used_in_tool_call')} + + + +
+ + {t('account:model.default_system_chat_prompt')} + + + + +
+ + {t('account:model.custom_cq_prompt')} + + + + +
+ + {t('account:model.custom_extract_prompt')} + + + + +
+ + {t('account:model.default_config')} + + + + { + if (!e) { + setValue('defaultConfig', {}); + return; + } + try { + setValue('defaultConfig', JSON.parse(e)); + } catch (error) { + console.error(error); + } + }} + {...InputStyles} + /> +
+
+ )} +
+
+ + {!modelData.isCustom && ( + + )} + + + +
+ ); +}; + +export default function Dom() { + return <>; +} diff --git a/projects/app/src/pageComponents/account/model/Channel/EditChannelModal.tsx b/projects/app/src/pageComponents/account/model/Channel/EditChannelModal.tsx new file mode 100644 index 000000000000..97943fd96b3a --- /dev/null +++ b/projects/app/src/pageComponents/account/model/Channel/EditChannelModal.tsx @@ -0,0 +1,499 @@ +import { aiproxyIdMap } from '@/global/aiproxy/constants'; +import { ChannelInfoType } from '@/global/aiproxy/type'; +import { + Box, + BoxProps, + Button, + Flex, + Input, + MenuItemProps, + ModalBody, + ModalFooter, + useDisclosure, + Menu, + MenuButton, + MenuList, + MenuItem, + HStack, + useOutsideClick +} from '@chakra-ui/react'; +import FormLabel from '@fastgpt/web/components/common/MyBox/FormLabel'; +import MyModal from '@fastgpt/web/components/common/MyModal'; +import MySelect from '@fastgpt/web/components/common/MySelect'; +import { useTranslation } from 'next-i18next'; +import React, { useCallback, useMemo, useRef, useState } from 'react'; +import { useForm } from 'react-hook-form'; +import { AddModelButton } from '../AddModelBox'; +import dynamic from 'next/dynamic'; +import { SystemModelItemType } from '@fastgpt/service/core/ai/type'; +import { ModelTypeEnum } from '@fastgpt/global/core/ai/model'; +import { useSystemStore } from '@/web/common/system/useSystemStore'; +import { getSystemModelList } from '@/web/core/ai/config'; +import { useRequest2 } from '@fastgpt/web/hooks/useRequest'; +import { getModelProvider } from '@fastgpt/global/core/ai/provider'; +import MyIcon from '@fastgpt/web/components/common/Icon'; +import MyAvatar from '@fastgpt/web/components/common/Avatar'; +import MyTag from '@fastgpt/web/components/common/Tag/index'; +import { useCopyData } from '@fastgpt/web/hooks/useCopyData'; +import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip'; +import JsonEditor from '@fastgpt/web/components/common/Textarea/JsonEditor'; +import { getChannelProviders, postCreateChannel, putChannel } from '@/web/core/ai/channel'; +import CopyBox from '@fastgpt/web/components/common/String/CopyBox'; + +const ModelEditModal = dynamic(() => import('../AddModelBox').then((mod) => mod.ModelEditModal)); + +const LabelStyles: BoxProps = { + fontSize: 'sm', + color: 'myGray.900', + flex: '0 0 70px' +}; +const EditChannelModal = ({ + defaultConfig, + onClose, + onSuccess +}: { + defaultConfig: ChannelInfoType; + onClose: () => void; + onSuccess: () => void; +}) => { + const { t } = useTranslation(); + const { defaultModels } = useSystemStore(); + const isEdit = defaultConfig.id !== 0; + + const { register, handleSubmit, watch, setValue } = useForm({ + defaultValues: defaultConfig + }); + + const providerType = watch('type'); + const { data: providerList = [] } = useRequest2( + () => + getChannelProviders().then((res) => { + return Object.entries(res) + .map(([key, value]) => { + const mapData = aiproxyIdMap[key as any] ?? { + label: value.name, + provider: 'Other' + }; + const provider = getModelProvider(mapData.provider); + return { + order: provider.order, + defaultBaseUrl: value.defaultBaseUrl, + keyHelp: value.keyHelp, + icon: provider.avatar, + label: t(mapData.label as any), + value: Number(key) + }; + }) + .sort((a, b) => a.order - b.order); + }), + { + manual: false + } + ); + const selectedProvider = useMemo(() => { + const res = providerList.find((item) => item.value === providerType); + return res; + }, [providerList, providerType]); + + const [editModelData, setEditModelData] = useState(); + const onCreateModel = (type: ModelTypeEnum) => { + const defaultModel = defaultModels[type]; + + setEditModelData({ + ...defaultModel, + model: '', + name: '', + charsPointsPrice: 0, + inputPrice: undefined, + outputPrice: undefined, + + isCustom: true, + isActive: true, + // @ts-ignore + type + }); + }; + + const models = watch('models'); + const { + data: systemModelList = [], + runAsync: refreshSystemModelList, + loading: loadingModels + } = useRequest2(getSystemModelList, { + manual: false + }); + const modelList = useMemo(() => { + const currentProvider = aiproxyIdMap[providerType]?.provider; + return systemModelList + .map((item) => { + const provider = getModelProvider(item.provider); + + return { + provider: item.provider, + icon: provider.avatar, + label: item.model, + value: item.model + }; + }) + .sort((a, b) => { + // sort by provider, same provider first + if (a.provider === currentProvider && b.provider !== currentProvider) return -1; + if (a.provider !== currentProvider && b.provider === currentProvider) return 1; + return 0; + }); + }, [providerType, systemModelList]); + + const modelMapping = watch('model_mapping'); + + const { runAsync: onSubmit, loading: loadingCreate } = useRequest2( + (data: ChannelInfoType) => { + if (data.models.length === 0) { + return Promise.reject(t('account_model:selected_model_empty')); + } + return isEdit ? putChannel(data) : postCreateChannel(data); + }, + { + onSuccess() { + onSuccess(); + onClose(); + }, + successToast: isEdit ? t('common:common.Update Success') : t('common:common.Create Success'), + manual: true + } + ); + + const isLoading = loadingModels || loadingCreate; + + return ( + <> + + + {/* Chnnel name */} + + + {t('account_model:channel_name')} + + + + {/* Provider */} + + + {t('account_model:channel_type')} + + + { + setValue('type', val); + }} + /> + + + {/* Model */} + + + + {t('account_model:model')}({models.length}) + + + + + + + { + setValue('models', val); + }} + /> + + + {/* Mapping */} + + + {t('account_model:mapping')} + + + + { + if (!val) { + setValue('model_mapping', {}); + } else { + try { + setValue('model_mapping', JSON.parse(val)); + } catch (error) {} + } + }} + /> + + + {/* url and key */} + + + {t('account_model:base_url')} + {selectedProvider && ( + + {'('} + {t('account_model:default_url')}: + + {selectedProvider?.defaultBaseUrl || ''} + + {')'} + + )} + + + + + + {t('account_model:api_key')} + {selectedProvider?.keyHelp && ( + + {'('} + {t('account_model:key_type')} + {selectedProvider.keyHelp} + {')'} + + )} + + + + + + + + + + {!!editModelData && ( + setEditModelData(undefined)} + /> + )} + + ); +}; +export default EditChannelModal; + +type SelectProps = { + list: { + icon?: string; + label: string; + value: string; + }[]; + value: string[]; + onSelect: (val: string[]) => void; +}; +const menuItemStyles: MenuItemProps = { + borderRadius: 'sm', + py: 2, + display: 'flex', + alignItems: 'center', + _hover: { + backgroundColor: 'myGray.100' + }, + _notLast: { + mb: 0.5 + } +}; +const MultipleSelect = ({ value = [], list = [], onSelect }: SelectProps) => { + const ref = useRef(null); + const BoxRef = useRef(null); + + const { t } = useTranslation(); + const { isOpen, onOpen, onClose } = useDisclosure(); + const { copyData } = useCopyData(); + + const onclickItem = useCallback( + (val: string) => { + if (value.includes(val)) { + onSelect(value.filter((i) => i !== val)); + } else { + onSelect([...value, val]); + BoxRef.current?.scrollTo({ + top: BoxRef.current.scrollHeight + }); + } + }, + [value, onSelect] + ); + + const [search, setSearch] = useState(''); + + const filterUnSelected = useMemo(() => { + return list + .filter((item) => !value.includes(item.value)) + .filter((item) => { + if (!search) return true; + const regx = new RegExp(search, 'i'); + return regx.test(item.label); + }); + }, [list, value, search]); + + useOutsideClick({ + ref, + handler: () => { + onClose(); + } + }); + + return ( + + + { + onOpen(); + setSearch(''); + } + })} + > + + + {value.length === 0 ? ( + + {t('account_model:select_model_placeholder')} + + ) : ( + + {value.map((item) => ( + { + e.stopPropagation(); + copyData(item, t('account_model:copy_model_id_success')); + }} + > + {item} + { + e.stopPropagation(); + onclickItem(item); + }} + /> + + ))} + {isOpen && ( + setSearch(e.target.value)} + placeholder={t('account_model:search_model')} + onClick={(e) => { + e.stopPropagation(); + }} + /> + )} + + )} + + + + + + {filterUnSelected.map((item, i) => { + return ( + { + onclickItem(item.value); + }} + whiteSpace={'pre-wrap'} + fontSize={'sm'} + gap={2} + {...menuItemStyles} + > + {item.icon && } + {item.label} + + ); + })} + + + + ); +}; diff --git a/projects/app/src/pageComponents/account/model/Channel/ModelTest.tsx b/projects/app/src/pageComponents/account/model/Channel/ModelTest.tsx new file mode 100644 index 000000000000..54895df06e89 --- /dev/null +++ b/projects/app/src/pageComponents/account/model/Channel/ModelTest.tsx @@ -0,0 +1,195 @@ +import { getSystemModelList, getTestModel } from '@/web/core/ai/config'; +import { + Table, + Thead, + Tbody, + Tr, + Th, + Td, + TableContainer, + Box, + Flex, + Button, + HStack, + ModalBody, + ModalFooter +} from '@chakra-ui/react'; +import { getModelProvider } from '@fastgpt/global/core/ai/provider'; +import { useRequest2 } from '@fastgpt/web/hooks/useRequest'; +import React, { useRef, useState } from 'react'; +import MyIcon from '@fastgpt/web/components/common/Icon'; +import { useTranslation } from 'next-i18next'; +import MyModal from '@fastgpt/web/components/common/MyModal'; +import MyTag from '@fastgpt/web/components/common/Tag/index'; +import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip'; +import { getErrText } from '@fastgpt/global/common/error/utils'; +import { batchRun } from '@fastgpt/global/common/system/utils'; +import { useToast } from '@fastgpt/web/hooks/useToast'; + +type ModelTestItem = { + label: React.ReactNode; + model: string; + status: 'waiting' | 'running' | 'success' | 'error'; + message?: string; + duration?: number; +}; + +const ModelTest = ({ models, onClose }: { models: string[]; onClose: () => void }) => { + const { t } = useTranslation(); + const { toast } = useToast(); + const [testModelList, setTestModelList] = useState([]); + + const statusMap = useRef({ + waiting: { + label: t('account_model:waiting_test'), + colorSchema: 'gray' + }, + running: { + label: t('account_model:running_test'), + colorSchema: 'blue' + }, + success: { + label: t('common:common.Success'), + colorSchema: 'green' + }, + error: { + label: t('common:common.failed'), + colorSchema: 'red' + } + }); + const { loading: loadingModels } = useRequest2(getSystemModelList, { + manual: false, + refreshDeps: [models], + onSuccess(res) { + const list = models + .map((model) => { + const modelData = res.find((item) => item.model === model); + if (!modelData) return null; + const provider = getModelProvider(modelData.provider); + + return { + label: ( + + + {t(modelData.name as any)} + + ), + model: modelData.model, + status: 'waiting' + }; + }) + .filter(Boolean) as ModelTestItem[]; + setTestModelList(list); + } + }); + + const { runAsync: onStartTest, loading: isTesting } = useRequest2( + async () => { + { + let errorNum = 0; + const testModel = async (model: string) => { + setTestModelList((prev) => + prev.map((item) => + item.model === model ? { ...item, status: 'running', message: '' } : item + ) + ); + const start = Date.now(); + try { + await getTestModel(model); + const duration = Date.now() - start; + setTestModelList((prev) => + prev.map((item) => + item.model === model + ? { ...item, status: 'success', duration: duration / 1000 } + : item + ) + ); + } catch (error) { + setTestModelList((prev) => + prev.map((item) => + item.model === model + ? { ...item, status: 'error', message: getErrText(error) } + : item + ) + ); + errorNum++; + } + }; + + await batchRun( + testModelList.map((item) => item.model), + testModel, + 5 + ); + + if (errorNum > 0) { + toast({ + status: 'warning', + title: t('account_model:test_failed', { num: errorNum }) + }); + } + } + }, + { + refreshDeps: [testModelList] + } + ); + + return ( + + + + + + + + + + + + {testModelList.map((item) => { + const data = statusMap.current[item.status]; + return ( + + + + + ); + })} + +
{t('account_model:model')}{t('account_model:channel_status')}
{item.label} + + + {data.label} + + {item.message && } + {item.status === 'success' && item.duration && ( + + {t('account_model:request_duration', { + duration: item.duration.toFixed(2) + })} + + )} + +
+
+
+ + + + +
+ ); +}; + +export default ModelTest; diff --git a/projects/app/src/pageComponents/account/model/Channel/index.tsx b/projects/app/src/pageComponents/account/model/Channel/index.tsx new file mode 100644 index 000000000000..795cfd521ea2 --- /dev/null +++ b/projects/app/src/pageComponents/account/model/Channel/index.tsx @@ -0,0 +1,230 @@ +import { deleteChannel, getChannelList, putChannel, putChannelStatus } from '@/web/core/ai/channel'; +import { useRequest2 } from '@fastgpt/web/hooks/useRequest'; +import React, { useState } from 'react'; +import { + Table, + Thead, + Tbody, + Tr, + Th, + Td, + TableContainer, + Box, + Flex, + Button, + HStack +} from '@chakra-ui/react'; +import { useTranslation } from 'next-i18next'; +import MyBox from '@fastgpt/web/components/common/MyBox'; +import MyIconButton from '@fastgpt/web/components/common/Icon/button'; +import { useUserStore } from '@/web/support/user/useUserStore'; +import { ChannelInfoType } from '@/global/aiproxy/type'; +import MyTag from '@fastgpt/web/components/common/Tag/index'; +import { + aiproxyIdMap, + ChannelStatusEnum, + ChannelStautsMap, + defaultChannel +} from '@/global/aiproxy/constants'; +import MyMenu from '@fastgpt/web/components/common/MyMenu'; +import dynamic from 'next/dynamic'; +import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip'; +import MyNumberInput from '@fastgpt/web/components/common/Input/NumberInput'; +import { getModelProvider } from '@fastgpt/global/core/ai/provider'; +import MyIcon from '@fastgpt/web/components/common/Icon'; + +const EditChannelModal = dynamic(() => import('./EditChannelModal'), { ssr: false }); +const ModelTest = dynamic(() => import('./ModelTest'), { ssr: false }); + +const ChannelTable = ({ Tab }: { Tab: React.ReactNode }) => { + const { t } = useTranslation(); + const { userInfo } = useUserStore(); + + const isRoot = userInfo?.username === 'root'; + + const { + data: channelList = [], + runAsync: refreshChannelList, + loading: loadingChannelList + } = useRequest2(getChannelList, { + manual: false + }); + + const [editChannel, setEditChannel] = useState(); + + const { runAsync: updateChannel, loading: loadingUpdateChannel } = useRequest2(putChannel, { + manual: true, + onSuccess: () => { + refreshChannelList(); + } + }); + const { runAsync: updateChannelStatus, loading: loadingUpdateChannelStatus } = useRequest2( + putChannelStatus, + { + onSuccess: () => { + refreshChannelList(); + } + } + ); + + const { runAsync: onDeleteChannel, loading: loadingDeleteChannel } = useRequest2(deleteChannel, { + manual: true, + onSuccess: () => { + refreshChannelList(); + } + }); + + const [testModels, setTestModels] = useState(); + + const isLoading = + loadingChannelList || + loadingUpdateChannel || + loadingDeleteChannel || + loadingUpdateChannelStatus; + + return ( + <> + {isRoot && ( + + {Tab} + + + + )} + + + + + + + + + + + + + + + {channelList.map((item) => { + const providerData = aiproxyIdMap[item.type]; + const provider = getModelProvider(providerData?.provider); + + return ( + + + + + + + + + ); + })} + +
ID{t('account_model:channel_name')}{t('account_model:channel_type')}{t('account_model:channel_status')} + {t('account_model:channel_priority')} + +
{item.id}{item.name} + {providerData ? ( + + + {t(providerData?.label as any)} + + ) : ( + 'Invalid provider' + )} + + + {t(ChannelStautsMap[item.status]?.label as any) || + t('account_model:channel_status_unknown')} + + + { + const val = (() => { + if (!e) return 1; + return e; + })(); + updateChannel({ + ...item, + priority: val + }); + }} + /> + + setTestModels(item.models) + }, + ...(item.status === ChannelStatusEnum.ChannelStatusEnabled + ? [ + { + icon: 'common/disable', + label: t('account_model:forbid_channel'), + onClick: () => + updateChannelStatus( + item.id, + ChannelStatusEnum.ChannelStatusDisabled + ) + } + ] + : [ + { + icon: 'common/enable', + label: t('account_model:enable_channel'), + onClick: () => + updateChannelStatus( + item.id, + ChannelStatusEnum.ChannelStatusEnabled + ) + } + ]), + { + icon: 'common/settingLight', + label: t('account_model:edit'), + onClick: () => setEditChannel(item) + }, + { + type: 'danger', + icon: 'delete', + label: t('common:common.Delete'), + onClick: () => onDeleteChannel(item.id) + } + ] + } + ]} + Button={} + /> +
+
+
+ + {!!editChannel && ( + setEditChannel(undefined)} + onSuccess={refreshChannelList} + /> + )} + {!!testModels && setTestModels(undefined)} />} + + ); +}; + +export default ChannelTable; diff --git a/projects/app/src/pageComponents/account/model/Log/index.tsx b/projects/app/src/pageComponents/account/model/Log/index.tsx new file mode 100644 index 000000000000..e2320b0ea67a --- /dev/null +++ b/projects/app/src/pageComponents/account/model/Log/index.tsx @@ -0,0 +1,417 @@ +import { getChannelList, getChannelLog, getLogDetail } from '@/web/core/ai/channel'; +import { getSystemModelList } from '@/web/core/ai/config'; +import { useUserStore } from '@/web/support/user/useUserStore'; +import { + Table, + Thead, + Tbody, + Tr, + Th, + Td, + TableContainer, + Box, + Flex, + Button, + HStack, + ModalBody, + Grid, + GridItem, + BoxProps +} from '@chakra-ui/react'; +import { getModelProvider } from '@fastgpt/global/core/ai/provider'; +import DateRangePicker, { DateRangeType } from '@fastgpt/web/components/common/DateRangePicker'; +import MyBox from '@fastgpt/web/components/common/MyBox'; +import FormLabel from '@fastgpt/web/components/common/MyBox/FormLabel'; +import MySelect from '@fastgpt/web/components/common/MySelect'; +import { useRequest2 } from '@fastgpt/web/hooks/useRequest'; +import { useScrollPagination } from '@fastgpt/web/hooks/useScrollPagination'; +import { addDays } from 'date-fns'; +import { useTranslation } from 'next-i18next'; +import React, { useCallback, useMemo, useState } from 'react'; +import MyIcon from '@fastgpt/web/components/common/Icon'; +import { formatTime2YMDHMS } from '@fastgpt/global/common/string/time'; +import MyModal from '@fastgpt/web/components/common/MyModal'; +import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip'; +import SearchInput from '@fastgpt/web/components/common/Input/SearchInput'; + +type LogDetailType = { + id: number; + request_id: string; + channelName: string | number; + model: React.JSX.Element; + duration: number; + request_at: string; + code: number; + prompt_tokens: number; + completion_tokens: number; + endpoint: string; + + content?: string; + request_body?: string; + response_body?: string; +}; +const ChannelLog = ({ Tab }: { Tab: React.ReactNode }) => { + const { t } = useTranslation(); + const { userInfo } = useUserStore(); + + const isRoot = userInfo?.username === 'root'; + const [filterProps, setFilterProps] = useState<{ + request_id?: string; + channelId?: string; + model?: string; + code_type: 'all' | 'success' | 'error'; + dateRange: DateRangeType; + }>({ + request_id: '', + code_type: 'all', + dateRange: { + from: (() => { + const today = addDays(new Date(), -1); + today.setHours(0, 0, 0, 0); + return today; + })(), + to: (() => { + const today = new Date(); + today.setHours(23, 59, 59, 999); + return today; + })() + } + }); + + const { data: channelList = [] } = useRequest2( + async () => { + const res = await getChannelList().then((res) => + res.map((item) => ({ + label: item.name, + value: `${item.id}` + })) + ); + return [ + { + label: t('common:common.All'), + value: '' + }, + ...res + ]; + }, + { + manual: false + } + ); + + const { data: systemModelList = [] } = useRequest2(getSystemModelList, { + manual: false + }); + const modelList = useMemo(() => { + const res = systemModelList + .map((item) => { + const provider = getModelProvider(item.provider); + + return { + order: provider.order, + icon: provider.avatar, + label: item.model, + value: item.model + }; + }) + .sort((a, b) => a.order - b.order); + return [ + { + label: t('common:common.All'), + value: '' + }, + ...res + ]; + }, [systemModelList, t]); + + const { data, isLoading, ScrollData } = useScrollPagination(getChannelLog, { + pageSize: 20, + refreshDeps: [filterProps], + params: { + request_id: filterProps.request_id, + channel: filterProps.channelId, + model_name: filterProps.model, + code_type: filterProps.code_type, + start_timestamp: filterProps.dateRange.from?.getTime() || 0, + end_timestamp: filterProps.dateRange.to?.getTime() || 0 + } + }); + + const formatData = useMemo(() => { + return data.map((item) => { + const duration = item.created_at - item.request_at; + const durationSecond = duration / 1000; + + const channelName = channelList.find((channel) => channel.value === `${item.channel}`)?.label; + + const model = systemModelList.find((model) => model.model === item.model); + const provider = getModelProvider(model?.provider); + + return { + id: item.id, + channelName: channelName || item.channel, + model: ( + + + {model?.model} + + ), + duration: durationSecond, + request_at: formatTime2YMDHMS(item.request_at), + code: item.code, + prompt_tokens: item.prompt_tokens, + completion_tokens: item.completion_tokens, + request_id: item.request_id, + endpoint: item.endpoint, + content: item.content + }; + }); + }, [channelList, data, systemModelList]); + + const [logDetail, setLogDetail] = useState(); + + return ( + <> + {isRoot && ( + + {Tab} + + + setFilterProps({ ...filterProps, request_id: e.target.value })} + /> + + + )} + + + {t('common:user.Time')} + + setFilterProps({ ...filterProps, dateRange: e })} + /> + + + + {t('account_model:channel_name')} + + + bg={'myGray.50'} + isSearch + list={channelList} + placeholder={t('account_model:select_channel')} + value={filterProps.channelId} + onchange={(val) => setFilterProps({ ...filterProps, channelId: val })} + /> + + + + {t('account_model:model_name')} + + + bg={'myGray.50'} + isSearch + list={modelList} + placeholder={t('account_model:select_model')} + value={filterProps.model} + onchange={(val) => setFilterProps({ ...filterProps, model: val })} + /> + + + + {t('account_model:log_status')} + + + bg={'myGray.50'} + list={[ + { label: t('common:common.All'), value: 'all' }, + { label: t('common:common.Success'), value: 'success' }, + { label: t('common:common.failed'), value: 'error' } + ]} + value={filterProps.code_type} + onchange={(val) => setFilterProps({ ...filterProps, code_type: val })} + /> + + + + + + + + + + + + + + + + + + + + {formatData.map((item, index) => ( + + + + + + + + + + ))} + +
{t('account_model:channel_name')}{t('account_model:model')}{t('account_model:model_tokens')}{t('account_model:duration')}{t('account_model:channel_status')}{t('account_model:request_at')}
{item.channelName}{item.model} + {item.prompt_tokens} / {item.completion_tokens} + 10 ? 'red.600' : ''}>{item.duration.toFixed(2)}s + {item.code} + {item.content && } + {item.request_at} + +
+
+
+
+ + {!!logDetail && setLogDetail(undefined)} />} + + ); +}; + +export default ChannelLog; + +const LogDetail = ({ data, onClose }: { data: LogDetailType; onClose: () => void }) => { + const { t } = useTranslation(); + const { data: detailData } = useRequest2( + async () => { + if (data.code === 200) return data; + const res = await getLogDetail(data.id); + return { + ...res, + ...data + }; + }, + { + manual: false + } + ); + + const Title = useCallback(({ children, ...props }: { children: React.ReactNode } & BoxProps) => { + return ( + + {children} + + ); + }, []); + const Container = useCallback( + ({ children, ...props }: { children: React.ReactNode } & BoxProps) => { + return ( + + {children} + + ); + }, + [] + ); + + return ( + + {detailData && ( + + {/* 基本信息表格 */} + + {/* 第一行 */} + + RequestID + {detailData?.request_id} + + + {t('account_model:channel_status')} + + {detailData?.code} + + + + Endpoint + {detailData?.endpoint} + + + {t('account_model:channel_name')} + {detailData?.channelName} + + + {t('account_model:request_at')} + {detailData?.request_at} + + + {t('account_model:duration')} + {detailData?.duration.toFixed(2)}s + + + {t('account_model:model')} + {detailData?.model} + + + {t('account_model:model_tokens')} + + {detailData?.prompt_tokens} / {detailData?.completion_tokens} + + + {detailData?.content && ( + + Content + {detailData?.content} + + )} + {detailData?.request_body && ( + + Request Body + {detailData?.request_body} + + )} + {detailData?.response_body && ( + + Response Body + {detailData?.response_body} + + )} + + + )} + + ); +}; diff --git a/projects/app/src/pageComponents/account/model/ModelConfigTable.tsx b/projects/app/src/pageComponents/account/model/ModelConfigTable.tsx index 30b2e4fc383b..7bf61393e94b 100644 --- a/projects/app/src/pageComponents/account/model/ModelConfigTable.tsx +++ b/projects/app/src/pageComponents/account/model/ModelConfigTable.tsx @@ -33,7 +33,6 @@ import { useRequest2 } from '@fastgpt/web/hooks/useRequest'; import { deleteSystemModel, getModelConfigJson, - getSystemModelDefaultConfig, getSystemModelDetail, getSystemModelList, getTestModel, @@ -44,24 +43,20 @@ import MyBox from '@fastgpt/web/components/common/MyBox'; import { SystemModelItemType } from '@fastgpt/service/core/ai/type'; import { useConfirm } from '@fastgpt/web/hooks/useConfirm'; import MyIconButton from '@fastgpt/web/components/common/Icon/button'; -import { useForm } from 'react-hook-form'; -import MyNumberInput from '@fastgpt/web/components/common/Input/NumberInput'; -import MyTextarea from '@/components/common/Textarea/MyTextarea'; import JsonEditor from '@fastgpt/web/components/common/Textarea/JsonEditor'; import { clientInitData } from '@/web/common/system/staticData'; import { useUserStore } from '@/web/support/user/useUserStore'; -import MyMenu from '@fastgpt/web/components/common/MyMenu'; import { useSystemStore } from '@/web/common/system/useSystemStore'; import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip'; import { putUpdateWithJson } from '@/web/core/ai/config'; import CopyBox from '@fastgpt/web/components/common/String/CopyBox'; import MyIcon from '@fastgpt/web/components/common/Icon'; import AIModelSelector from '@/components/Select/AIModelSelector'; -import { useRefresh } from '../../../../../../packages/web/hooks/useRefresh'; -import { Prompt_CQJson, Prompt_ExtractJson } from '@fastgpt/global/core/ai/prompt/agent'; import MyDivider from '@fastgpt/web/components/common/MyDivider'; +import { AddModelButton } from './AddModelBox'; const MyModal = dynamic(() => import('@fastgpt/web/components/common/MyModal')); +const ModelEditModal = dynamic(() => import('./AddModelBox').then((mod) => mod.ModelEditModal)); const ModelTable = ({ Tab }: { Tab: React.ReactNode }) => { const { t } = useTranslation(); @@ -271,6 +266,7 @@ const ModelTable = ({ Tab }: { Tab: React.ReactNode }) => { } } ); + const onCreateModel = (type: ModelTypeEnum) => { const defaultModel = defaultModels[type]; @@ -316,37 +312,7 @@ const ModelTable = ({ Tab }: { Tab: React.ReactNode }) => { - {t('account:create_model')}} - menuList={[ - { - children: [ - { - label: t('common:model.type.chat'), - onClick: () => onCreateModel(ModelTypeEnum.llm) - }, - { - label: t('common:model.type.embedding'), - onClick: () => onCreateModel(ModelTypeEnum.embedding) - }, - { - label: t('common:model.type.tts'), - onClick: () => onCreateModel(ModelTypeEnum.tts) - }, - { - label: t('common:model.type.stt'), - onClick: () => onCreateModel(ModelTypeEnum.stt) - }, - { - label: t('common:model.type.reRank'), - onClick: () => onCreateModel(ModelTypeEnum.rerank) - } - ] - } - ]} - /> + )} @@ -512,650 +478,6 @@ const ModelTable = ({ Tab }: { Tab: React.ReactNode }) => { ); }; -const InputStyles = { - maxW: '300px', - bg: 'myGray.50', - w: '100%', - rows: 3 -}; -const ModelEditModal = ({ - modelData, - onSuccess, - onClose -}: { - modelData: SystemModelItemType; - onSuccess: () => void; - onClose: () => void; -}) => { - const { t } = useTranslation(); - const { feConfigs } = useSystemStore(); - - const { register, getValues, setValue, handleSubmit, watch, reset } = - useForm({ - defaultValues: modelData - }); - - const isCustom = !!modelData.isCustom; - const isLLMModel = modelData?.type === ModelTypeEnum.llm; - const isEmbeddingModel = modelData?.type === ModelTypeEnum.embedding; - const isTTSModel = modelData?.type === ModelTypeEnum.tts; - const isSTTModel = modelData?.type === ModelTypeEnum.stt; - const isRerankModel = modelData?.type === ModelTypeEnum.rerank; - - const provider = watch('provider'); - const providerData = useMemo(() => getModelProvider(provider), [provider]); - - const providerList = useRef<{ label: any; value: ModelProviderIdType }[]>( - ModelProviderList.map((item) => ({ - label: ( - - - {t(item.name as any)} - - ), - value: item.id - })) - ); - - const priceUnit = useMemo(() => { - if (isLLMModel || isEmbeddingModel) return '/ 1k Tokens'; - if (isTTSModel) return `/ 1k ${t('common:unit.character')}`; - if (isSTTModel) return `/ 60 ${t('common:unit.seconds')}`; - return ''; - return ''; - }, [isLLMModel, isEmbeddingModel, isTTSModel, t, isSTTModel]); - - const { runAsync: updateModel, loading: updatingModel } = useRequest2( - async (data: SystemModelItemType) => { - return putSystemModel({ - model: data.model, - metadata: data - }).then(onSuccess); - }, - { - onSuccess: () => { - onClose(); - }, - successToast: t('common:common.Success') - } - ); - - const [key, setKey] = useState(0); - const { runAsync: loadDefaultConfig, loading: loadingDefaultConfig } = useRequest2( - getSystemModelDefaultConfig, - { - onSuccess(res) { - reset({ - ...getValues(), - ...res - }); - setTimeout(() => { - setKey((prev) => prev + 1); - }, 0); - } - } - ); - - return ( - - - - - - - - - - - - - - - - - - - - - - - - - {priceUnit && feConfigs?.isPlus && ( - <> - - - - - {isLLMModel && ( - <> - - - - - - - - - - )} - - )} - {isLLMModel && ( - <> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - )} - {isEmbeddingModel && ( - <> - - - - - - - - - - - - - - - - - - )} - {isTTSModel && ( - <> - - - - - - )} - - - - - - - - - -
{t('account:model.param_name')}
- - {t('account:model.model_id')} - - - - {isCustom ? ( - - ) : ( - modelData?.model - )} -
{t('common:model.provider')} - setValue('provider', value)} - list={providerList.current} - {...InputStyles} - /> -
- - {t('account:model.alias')} - - - - -
- - {t('account:model.charsPointsPrice')} - - - - - - - {priceUnit} - - -
- - {t('account:model.input_price')} - - - - - - - {priceUnit} - - -
- - {t('account:model.output_price')} - - - - - - - {priceUnit} - - -
{t('common:core.ai.Max context')} - - - -
{t('account:model.max_quote')} - - - -
{t('common:core.chat.response.module maxToken')} - - - -
{t('account:model.max_temperature')} - - - -
- - {t('account:model.show_top_p')} - - - - - -
- - {t('account:model.show_stop_sign')} - - - - - -
{t('account:model.response_format')} - { - if (!e) { - setValue('responseFormatList', []); - return; - } - try { - setValue('responseFormatList', JSON.parse(e)); - } catch (error) { - console.error(error); - } - }} - {...InputStyles} - /> -
- - {t('account:model.normalization')} - - - - - - -
- - {t('account:model.default_token')} - - - - - - -
{t('common:core.ai.Max context')} - - - -
- - {t('account:model.defaultConfig')} - - - - - { - if (!e) { - setValue('defaultConfig', {}); - return; - } - try { - setValue('defaultConfig', JSON.parse(e)); - } catch (error) { - console.error(error); - } - }} - {...InputStyles} - /> - -
- - {t('account:model.voices')} - - - - - { - try { - setValue('voices', JSON.parse(e)); - } catch (error) { - console.error(error); - } - }} - {...InputStyles} - /> - -
- - {t('account:model.request_url')} - - - - -
- - {t('account:model.request_auth')} - - - - -
-
- {isLLMModel && ( - - - - - - - - - - - - - - - - - - - - - - - - - - {feConfigs?.isPlus && ( - - - - - )} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
{t('account:model.param_name')}
- - {t('account:model.tool_choice')} - - - - - - -
- - {t('account:model.function_call')} - - - - - - -
- - {t('account:model.vision')} - - - - - - -
- - {t('account:model.reasoning')} - - - - - - -
- - {t('account:model.censor')} - - - - - - -
{t('account:model.dataset_process')} - - - -
{t('account:model.used_in_classify')} - - - -
{t('account:model.used_in_extract_fields')} - - - -
{t('account:model.used_in_tool_call')} - - - -
- - {t('account:model.default_system_chat_prompt')} - - - - -
- - {t('account:model.custom_cq_prompt')} - - - - -
- - {t('account:model.custom_extract_prompt')} - - - - -
- - {t('account:model.default_config')} - - - - { - if (!e) { - setValue('defaultConfig', {}); - return; - } - try { - setValue('defaultConfig', JSON.parse(e)); - } catch (error) { - console.error(error); - } - }} - {...InputStyles} - /> -
-
- )} -
-
- - {!modelData.isCustom && ( - - )} - - - -
- ); -}; - const JsonConfigModal = ({ onClose, onSuccess @@ -1241,8 +563,10 @@ const DefaultModelModal = ({ embeddingModelList, ttsModelList, sttModelList, - reRankModelList + reRankModelList, + getVlmModelList } = useSystemStore(); + const vlmModelList = useMemo(() => getVlmModelList(), [getVlmModelList]); // Create a copy of defaultModels for local state management const [defaultData, setDefaultData] = useState(defaultModels); @@ -1381,6 +705,28 @@ const DefaultModelModal = ({ />
+ + + {t('account_model:vlm_model')} + + + + ({ + value: item.model, + label: item.name + }))} + onchange={(e) => { + setDefaultData((state) => ({ + ...state, + datasetImageLLM: vlmModelList.find((item) => item.model === e) + })); + }} + /> + +
{t('account_usage:output_token_length')}{t('account_usage:text_length')}{t('account_usage:duration_seconds')}{t('account_usage:pages')}{t('account_usage:total_points_consumed')}
{item.outputTokens ?? '-'}{item.charsLength ?? '-'}{item.duration ?? '-'}{item.pages ?? '-'}{formatNumber(item.amount)}
- - - - - - - - - - - - {usages.map((item) => ( - - - - - - - + + + +
{t('common:user.Time')}{t('account_usage:member')}{t('account_usage:user_type')}{t('account_usage:project_name')}{t('account_usage:total_points')}
{dayjs(item.time).format('YYYY/MM/DD HH:mm:ss')} - - - {item.sourceMember.name} - - {t(UsageSourceMap[item.source]?.label as any) || '-'}{t(item.appName as any) || '-'}{formatNumber(item.totalPoints) || 0} - -
+ + + + + + + + - ))} - -
{t('common:user.Time')}{t('account_usage:member')}{t('account_usage:user_type')}{t('account_usage:project_name')}{t('account_usage:total_points')}
- {!isLoading && usages.length === 0 && ( - - )} - + + + {usages.map((item) => ( + + {dayjs(item.time).format('YYYY/MM/DD HH:mm:ss')} + + + + {item.sourceMember.name} + + + {t(UsageSourceMap[item.source]?.label as any) || '-'} + {t(item.appName as any) || '-'} + {formatNumber(item.totalPoints) || 0} + + + + + ))} + + + {!isLoading && usages.length === 0 && ( + + )} + + diff --git a/projects/app/src/pageComponents/app/detail/Publish/Link/index.tsx b/projects/app/src/pageComponents/app/detail/Publish/Link/index.tsx index 61f86b6b0ddb..8280df91f0ce 100644 --- a/projects/app/src/pageComponents/app/detail/Publish/Link/index.tsx +++ b/projects/app/src/pageComponents/app/detail/Publish/Link/index.tsx @@ -353,7 +353,7 @@ function EditLinkModal({ QPM - + {t('common:support.outlink.share.Response Quote')} @@ -428,7 +428,7 @@ function EditLinkModal({ {t('common:support.outlink.share.show_complete_quote')} import('../Import/components/FileSourceSelector')); @@ -48,7 +50,7 @@ const Header = ({}: {}) => { const { t } = useTranslation(); const theme = useTheme(); - const { setLoading, feConfigs } = useSystemStore(); + const { feConfigs } = useSystemStore(); const datasetDetail = useContextSelector(DatasetPageContext, (v) => v.datasetDetail); const router = useRouter(); @@ -69,50 +71,36 @@ const Header = ({}: {}) => { tip: t('common:dataset.Manual collection Tip'), canEmpty: false }); + const { isOpen: isOpenFileSourceSelector, onOpen: onOpenFileSourceSelector, onClose: onCloseFileSourceSelector } = useDisclosure(); - const { mutate: onCreateCollection } = useRequest({ - mutationFn: async ({ - name, - type, - callback, - ...props - }: { - name: string; - type: DatasetCollectionTypeEnum; - callback?: (id: string) => void; - trainingType?: TrainingModeEnum; - rawLink?: string; - chunkSize?: number; - }) => { - setLoading(true); + + const { runAsync: onCreateCollection, loading: onCreating } = useRequest2( + async ({ name, type }: { name: string; type: DatasetCollectionTypeEnum }) => { const id = await postDatasetCollection({ parentId, datasetId: datasetDetail._id, name, - type, - ...props + type }); - callback?.(id); return id; }, - onSuccess() { - getData(pageNum); - }, - onSettled() { - setLoading(false); - }, + { + onSuccess() { + getData(pageNum); + }, + successToast: t('common:common.Create Success'), + errorToast: t('common:common.Create Failed') + } + ); - successToast: t('common:common.Create Success'), - errorToast: t('common:common.Create Failed') - }); const isWebSite = datasetDetail?.type === DatasetTypeEnum.websiteDataset; return ( - + { )} {isOpenFileSourceSelector && } - + ); }; diff --git a/projects/app/src/pageComponents/dataset/detail/CollectionCard/index.tsx b/projects/app/src/pageComponents/dataset/detail/CollectionCard/index.tsx index ace71ab0459a..1c5c998b722f 100644 --- a/projects/app/src/pageComponents/dataset/detail/CollectionCard/index.tsx +++ b/projects/app/src/pageComponents/dataset/detail/CollectionCard/index.tsx @@ -29,7 +29,8 @@ import { DatasetCollectionTypeEnum, DatasetStatusEnum, DatasetCollectionSyncResultMap, - DatasetTypeEnum + DatasetTypeEnum, + DatasetCollectionDataProcessModeMap } from '@fastgpt/global/core/dataset/constants'; import { getCollectionIcon } from '@fastgpt/global/core/dataset/utils'; import { TabEnum } from '../../../../pages/dataset/detail/index'; @@ -44,10 +45,7 @@ import { CollectionPageContext } from './Context'; import { DatasetPageContext } from '@/web/core/dataset/context/datasetPageContext'; import { formatTime2YMDHM } from '@fastgpt/global/common/string/time'; import MyTag from '@fastgpt/web/components/common/Tag/index'; -import { - checkCollectionIsFolder, - getTrainingTypeLabel -} from '@fastgpt/global/core/dataset/collection/utils'; +import { checkCollectionIsFolder } from '@fastgpt/global/core/dataset/collection/utils'; import { useFolderDrag } from '@/components/common/folder/useFolderDrag'; import TagsPopOver from './TagsPopOver'; import { useSystemStore } from '@/web/common/system/useSystemStore'; @@ -194,7 +192,7 @@ const CollectionCard = () => { {t('common:common.Name')} - {t('dataset:collection.Training type')} + {t('dataset:collection.training_type')} {t('dataset:collection_data_count')} {t('dataset:collection.Create update time')} {t('common:common.Status')} @@ -251,7 +249,14 @@ const CollectionCard = () => { {!checkCollectionIsFolder(collection.type) ? ( - <>{t((getTrainingTypeLabel(collection.trainingType) || '-') as any)} + <> + {collection.trainingType + ? t( + (DatasetCollectionDataProcessModeMap[collection.trainingType] + ?.label || '-') as any + ) + : '-'} + ) : ( '-' )} diff --git a/projects/app/src/pageComponents/dataset/detail/Import/Context.tsx b/projects/app/src/pageComponents/dataset/detail/Import/Context.tsx index 52eacd9bfae8..853efddecd3c 100644 --- a/projects/app/src/pageComponents/dataset/detail/Import/Context.tsx +++ b/projects/app/src/pageComponents/dataset/detail/Import/Context.tsx @@ -1,13 +1,16 @@ import { useRouter } from 'next/router'; -import { SetStateAction, useState } from 'react'; +import { SetStateAction, useMemo, useState } from 'react'; import { useTranslation } from 'next-i18next'; import { createContext, useContextSelector } from 'use-context-selector'; -import { ImportDataSourceEnum, TrainingModeEnum } from '@fastgpt/global/core/dataset/constants'; +import { + DatasetCollectionDataProcessModeEnum, + ImportDataSourceEnum +} from '@fastgpt/global/core/dataset/constants'; import { useMyStep } from '@fastgpt/web/hooks/useStep'; import { Box, Button, Flex, IconButton } from '@chakra-ui/react'; import MyIcon from '@fastgpt/web/components/common/Icon'; import { TabEnum } from '../NavBar'; -import { ImportProcessWayEnum } from '@/web/core/dataset/constants'; +import { ChunkSettingModeEnum } from '@/web/core/dataset/constants'; import { UseFormReturn, useForm } from 'react-hook-form'; import { ImportSourceItemType } from '@/web/core/dataset/type'; import { Prompt_AgentQA } from '@fastgpt/global/core/ai/prompt/agent'; @@ -19,12 +22,10 @@ type TrainingFiledType = { minChunkSize: number; autoChunkSize: number; chunkSize: number; - showChunkInput: boolean; - showPromptInput: boolean; charsPointsPrice: number; priceTip: string; uploadRate: number; - chunkSizeField?: ChunkSizeFieldType; + chunkSizeField: ChunkSizeFieldType; }; type DatasetImportContextType = { importSource: ImportDataSourceEnum; @@ -39,8 +40,13 @@ type DatasetImportContextType = { type ChunkSizeFieldType = 'embeddingChunkSize' | 'qaChunkSize'; export type ImportFormType = { - mode: TrainingModeEnum; - way: ImportProcessWayEnum; + customPdfParse: boolean; + + trainingType: DatasetCollectionDataProcessModeEnum; + imageIndex: boolean; + autoIndexes: boolean; + + chunkSettingMode: ChunkSettingModeEnum; embeddingChunkSize: number; qaChunkSize: number; customSplitChar: string; @@ -58,8 +64,6 @@ export const DatasetImportContext = createContext({ maxChunkSize: 0, minChunkSize: 0, - showChunkInput: false, - showPromptInput: false, sources: [], setSources: function (value: SetStateAction): void { throw new Error('Function not implemented.'); @@ -88,72 +92,93 @@ const DatasetImportContextProvider = ({ children }: { children: React.ReactNode const modeSteps: Record = { [ImportDataSourceEnum.reTraining]: [ { title: t('dataset:core.dataset.import.Adjust parameters') }, - { title: t('common:core.dataset.import.Upload data') } + { + title: t('dataset:import_data_preview') + }, + { title: t('dataset:import_confirm') } ], [ImportDataSourceEnum.fileLocal]: [ { - title: t('common:core.dataset.import.Select file') + title: t('dataset:import_select_file') + }, + { + title: t('dataset:import_param_setting') }, { - title: t('common:core.dataset.import.Data Preprocessing') + title: t('dataset:import_data_preview') }, { - title: t('common:core.dataset.import.Upload data') + title: t('dataset:import_confirm') } ], [ImportDataSourceEnum.fileLink]: [ { - title: t('common:core.dataset.import.Select file') + title: t('dataset:import_select_file') + }, + { + title: t('dataset:import_param_setting') }, { - title: t('common:core.dataset.import.Data Preprocessing') + title: t('dataset:import_data_preview') }, { - title: t('common:core.dataset.import.Upload data') + title: t('dataset:import_confirm') } ], [ImportDataSourceEnum.fileCustom]: [ { - title: t('common:core.dataset.import.Select file') + title: t('dataset:import_select_file') + }, + { + title: t('dataset:import_param_setting') }, { - title: t('common:core.dataset.import.Data Preprocessing') + title: t('dataset:import_data_preview') }, { - title: t('common:core.dataset.import.Upload data') + title: t('dataset:import_confirm') } ], [ImportDataSourceEnum.csvTable]: [ { - title: t('common:core.dataset.import.Select file') + title: t('dataset:import_select_file') }, { - title: t('common:core.dataset.import.Data Preprocessing') + title: t('dataset:import_param_setting') }, { - title: t('common:core.dataset.import.Upload data') + title: t('dataset:import_data_preview') + }, + { + title: t('dataset:import_confirm') } ], [ImportDataSourceEnum.externalFile]: [ { - title: t('common:core.dataset.import.Select file') + title: t('dataset:import_select_file') }, { - title: t('common:core.dataset.import.Data Preprocessing') + title: t('dataset:import_param_setting') }, { - title: t('common:core.dataset.import.Upload data') + title: t('dataset:import_data_preview') + }, + { + title: t('dataset:import_confirm') } ], [ImportDataSourceEnum.apiDataset]: [ { - title: t('common:core.dataset.import.Select file') + title: t('dataset:import_select_file') + }, + { + title: t('dataset:import_param_setting') }, { - title: t('common:core.dataset.import.Data Preprocessing') + title: t('dataset:import_data_preview') }, { - title: t('common:core.dataset.import.Upload data') + title: t('dataset:import_confirm') } ] }; @@ -168,96 +193,114 @@ const DatasetImportContextProvider = ({ children }: { children: React.ReactNode const processParamsForm = useForm({ defaultValues: { - mode: TrainingModeEnum.chunk, - way: ImportProcessWayEnum.auto, + imageIndex: false, + autoIndexes: false, + + trainingType: DatasetCollectionDataProcessModeEnum.chunk, + + chunkSettingMode: ChunkSettingModeEnum.auto, embeddingChunkSize: vectorModel?.defaultToken || 512, qaChunkSize: Math.min(agentModel.maxResponse * 1, agentModel.maxContext * 0.7), customSplitChar: '', qaPrompt: Prompt_AgentQA.description, - webSelector: '' + webSelector: '', + customPdfParse: false } }); const [sources, setSources] = useState([]); // watch form - const mode = processParamsForm.watch('mode'); - const way = processParamsForm.watch('way'); + const trainingType = processParamsForm.watch('trainingType'); + const chunkSettingMode = processParamsForm.watch('chunkSettingMode'); const embeddingChunkSize = processParamsForm.watch('embeddingChunkSize'); const qaChunkSize = processParamsForm.watch('qaChunkSize'); const customSplitChar = processParamsForm.watch('customSplitChar'); + const autoIndexes = processParamsForm.watch('autoIndexes'); - const modeStaticParams: Record = { - [TrainingModeEnum.auto]: { - chunkOverlapRatio: 0.2, - maxChunkSize: 2048, - minChunkSize: 100, - autoChunkSize: vectorModel?.defaultToken ? vectorModel?.defaultToken * 2 : 1024, - chunkSize: vectorModel?.defaultToken ? vectorModel?.defaultToken * 2 : 1024, - showChunkInput: false, - showPromptInput: false, - charsPointsPrice: agentModel.charsPointsPrice || 0, - priceTip: t('dataset:import.Auto mode Estimated Price Tips', { - price: agentModel.charsPointsPrice - }), - uploadRate: 100 - }, - [TrainingModeEnum.chunk]: { - chunkSizeField: 'embeddingChunkSize' as ChunkSizeFieldType, - chunkOverlapRatio: 0.2, - maxChunkSize: vectorModel?.maxToken || 512, - minChunkSize: 100, - autoChunkSize: vectorModel?.defaultToken || 512, - chunkSize: embeddingChunkSize, - showChunkInput: true, - showPromptInput: false, - charsPointsPrice: vectorModel.charsPointsPrice || 0, - priceTip: t('dataset:import.Embedding Estimated Price Tips', { - price: vectorModel.charsPointsPrice - }), - uploadRate: 150 - }, - [TrainingModeEnum.qa]: { - chunkSizeField: 'qaChunkSize' as ChunkSizeFieldType, - chunkOverlapRatio: 0, - maxChunkSize: Math.min(agentModel.maxResponse * 4, agentModel.maxContext * 0.7), - minChunkSize: 4000, - autoChunkSize: Math.min(agentModel.maxResponse * 1, agentModel.maxContext * 0.7), - chunkSize: qaChunkSize, - showChunkInput: true, - showPromptInput: true, - charsPointsPrice: agentModel.charsPointsPrice || 0, - priceTip: t('dataset:import.Auto mode Estimated Price Tips', { - price: agentModel.charsPointsPrice - }), - uploadRate: 30 + const TrainingModeMap = useMemo(() => { + if (trainingType === DatasetCollectionDataProcessModeEnum.qa) { + return { + chunkSizeField: 'qaChunkSize', + chunkOverlapRatio: 0, + maxChunkSize: Math.min(agentModel.maxResponse * 4, agentModel.maxContext * 0.7), + minChunkSize: 4000, + autoChunkSize: Math.min(agentModel.maxResponse * 1, agentModel.maxContext * 0.7), + chunkSize: qaChunkSize, + charsPointsPrice: agentModel.charsPointsPrice || 0, + priceTip: t('dataset:import.Auto mode Estimated Price Tips', { + price: agentModel.charsPointsPrice + }), + uploadRate: 30 + }; + } else if (autoIndexes) { + return { + chunkSizeField: 'embeddingChunkSize', + chunkOverlapRatio: 0.2, + maxChunkSize: 2048, + minChunkSize: 100, + autoChunkSize: vectorModel?.defaultToken ? vectorModel.defaultToken * 2 : 1024, + chunkSize: embeddingChunkSize, + charsPointsPrice: agentModel.charsPointsPrice || 0, + priceTip: t('dataset:import.Auto mode Estimated Price Tips', { + price: agentModel.charsPointsPrice + }), + uploadRate: 100 + }; + } else { + return { + chunkSizeField: 'embeddingChunkSize', + chunkOverlapRatio: 0.2, + maxChunkSize: vectorModel?.maxToken || 512, + minChunkSize: 100, + autoChunkSize: vectorModel?.defaultToken || 512, + chunkSize: embeddingChunkSize, + charsPointsPrice: vectorModel.charsPointsPrice || 0, + priceTip: t('dataset:import.Embedding Estimated Price Tips', { + price: vectorModel.charsPointsPrice + }), + uploadRate: 150 + }; } - }; - const selectModelStaticParam = modeStaticParams[mode]; + }, [ + trainingType, + autoIndexes, + agentModel.maxResponse, + agentModel.maxContext, + agentModel.charsPointsPrice, + qaChunkSize, + t, + vectorModel.defaultToken, + vectorModel?.maxToken, + vectorModel.charsPointsPrice, + embeddingChunkSize + ]); - const wayStaticPrams = { - [ImportProcessWayEnum.auto]: { - chunkSize: selectModelStaticParam.autoChunkSize, - customSplitChar: '' - }, - [ImportProcessWayEnum.custom]: { - chunkSize: modeStaticParams[mode].chunkSize, - customSplitChar + const chunkSettingModeMap = useMemo(() => { + if (chunkSettingMode === ChunkSettingModeEnum.auto) { + return { + chunkSize: TrainingModeMap.autoChunkSize, + customSplitChar: '' + }; + } else { + return { + chunkSize: TrainingModeMap.chunkSize, + customSplitChar + }; } - }; - const chunkSize = wayStaticPrams[way].chunkSize; + }, [chunkSettingMode, TrainingModeMap.autoChunkSize, TrainingModeMap.chunkSize, customSplitChar]); const contextValue = { + ...TrainingModeMap, + ...chunkSettingModeMap, importSource: source, parentId, activeStep, goToNext, processParamsForm, - ...selectModelStaticParam, sources, - setSources, - chunkSize + setSources }; return ( diff --git a/projects/app/src/pageComponents/dataset/detail/Import/commonProgress/DataProcess.tsx b/projects/app/src/pageComponents/dataset/detail/Import/commonProgress/DataProcess.tsx index c5e30ed49086..6daae5d73ec7 100644 --- a/projects/app/src/pageComponents/dataset/detail/Import/commonProgress/DataProcess.tsx +++ b/projects/app/src/pageComponents/dataset/detail/Import/commonProgress/DataProcess.tsx @@ -1,4 +1,4 @@ -import React, { useCallback, useMemo, useRef } from 'react'; +import React, { useCallback, useEffect, useMemo, useRef } from 'react'; import { Box, Flex, @@ -7,45 +7,48 @@ import { ModalBody, ModalFooter, Textarea, - useDisclosure + useDisclosure, + Checkbox, + Accordion, + AccordionItem, + AccordionButton, + AccordionPanel, + AccordionIcon, + HStack } from '@chakra-ui/react'; import MyIcon from '@fastgpt/web/components/common/Icon'; import { useTranslation } from 'next-i18next'; import LeftRadio from '@fastgpt/web/components/common/Radio/LeftRadio'; -import { TrainingModeEnum, TrainingTypeMap } from '@fastgpt/global/core/dataset/constants'; -import { ImportProcessWayEnum } from '@/web/core/dataset/constants'; +import { + DatasetCollectionDataProcessModeEnum, + DatasetCollectionDataProcessModeMap +} from '@fastgpt/global/core/dataset/constants'; +import { ChunkSettingModeEnum } from '@/web/core/dataset/constants'; import MyTooltip from '@fastgpt/web/components/common/MyTooltip'; import { useSystemStore } from '@/web/common/system/useSystemStore'; import MyModal from '@fastgpt/web/components/common/MyModal'; import { Prompt_AgentQA } from '@fastgpt/global/core/ai/prompt/agent'; -import Preview from '../components/Preview'; import MyTag from '@fastgpt/web/components/common/Tag/index'; import { useContextSelector } from 'use-context-selector'; import { DatasetImportContext } from '../Context'; -import { useToast } from '@fastgpt/web/hooks/useToast'; import FormLabel from '@fastgpt/web/components/common/MyBox/FormLabel'; import MyNumberInput from '@fastgpt/web/components/common/Input/NumberInput'; import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip'; +import { shadowLight } from '@fastgpt/web/styles/theme'; +import { DatasetPageContext } from '@/web/core/dataset/context/datasetPageContext'; +import { useToast } from '@fastgpt/web/hooks/useToast'; -function DataProcess({ showPreviewChunks = true }: { showPreviewChunks: boolean }) { +function DataProcess() { const { t } = useTranslation(); const { feConfigs } = useSystemStore(); + const { toast } = useToast(); - const { - goToNext, - processParamsForm, - chunkSizeField, - minChunkSize, - showChunkInput, - showPromptInput, - maxChunkSize, - priceTip, - chunkSize - } = useContextSelector(DatasetImportContext, (v) => v); + const { goToNext, processParamsForm, chunkSizeField, minChunkSize, maxChunkSize } = + useContextSelector(DatasetImportContext, (v) => v); + const datasetDetail = useContextSelector(DatasetPageContext, (v) => v.datasetDetail); const { getValues, setValue, register, watch } = processParamsForm; - const { toast } = useToast(); - const mode = watch('mode'); - const way = watch('way'); + const trainingType = watch('trainingType'); + const chunkSettingMode = watch('chunkSettingMode'); const { isOpen: isOpenCustomPrompt, @@ -54,214 +57,317 @@ function DataProcess({ showPreviewChunks = true }: { showPreviewChunks: boolean } = useDisclosure(); const trainingModeList = useMemo(() => { - const list = Object.entries(TrainingTypeMap); - return list; + const list = Object.entries(DatasetCollectionDataProcessModeMap); + return list + .filter(([key]) => key !== DatasetCollectionDataProcessModeEnum.auto) + .map(([key, value]) => ({ + title: t(value.label as any), + value: key as DatasetCollectionDataProcessModeEnum, + tooltip: t(value.tooltip as any) + })); }, []); - const onSelectTrainWay = useCallback( - (e: TrainingModeEnum) => { - if (!feConfigs?.isPlus && !TrainingTypeMap[e]?.openSource) { - return toast({ - status: 'warning', - title: t('common:common.system.Commercial version function') - }); - } - setValue('mode', e); - }, - [feConfigs?.isPlus, setValue, t, toast] - ); - - return ( - - - - - {t('dataset:data_process_setting')} - - - - {t('dataset:training_mode')} - ({ - title: t(value.label as any), - value: key, - tooltip: t(value.tooltip as any) - }))} - px={3} - py={2} - value={mode} - onChange={onSelectTrainWay} - defaultBg="white" - activeBg="white" - display={'flex'} - flexWrap={'wrap'} - /> + const Title = useCallback(({ title }: { title: string }) => { + return ( + + + + {title} + + + ); + }, []); - - {t('dataset:data_process_params')} - - {showChunkInput && chunkSizeField && ( - - - {t('dataset:ideal_chunk_length')} - - - span': { - display: 'block' - } - }} - > - - { - if (e === undefined) return; - setValue(chunkSizeField, +e); - }} - /> - - - - )} + // Adapt auto training + useEffect(() => { + if (trainingType === DatasetCollectionDataProcessModeEnum.auto) { + setValue('autoIndexes', true); + setValue('trainingType', DatasetCollectionDataProcessModeEnum.chunk); + } + }, [trainingType, setValue]); - - - {t('common:core.dataset.import.Custom split char')} - - - - - - + const showFileParseSetting = feConfigs?.showCustomPdfParse; + const showQAPromptInput = trainingType === DatasetCollectionDataProcessModeEnum.qa; - {showPromptInput && ( - - {t('common:core.dataset.collection.QA Prompt')} - + + + {showFileParseSetting && ( + + + + <AccordionPanel p={2}> + <Flex + flexDirection={'column'} + gap={3} + border={'1px solid'} + borderColor={'primary.600'} + borderRadius={'md'} + boxShadow={shadowLight} + p={4} + > + {feConfigs.showCustomPdfParse && ( + <HStack spacing={1}> + <Checkbox {...register('customPdfParse')}> + <FormLabel>{t('dataset:pdf_enhance_parse')}</FormLabel> + </Checkbox> + <QuestionTip label={t('dataset:pdf_enhance_parse_tips')} /> + {feConfigs?.show_pay && ( + <MyTag + type={'borderSolid'} + borderColor={'myGray.200'} + bg={'myGray.100'} + color={'primary.600'} + py={1.5} borderRadius={'md'} - maxH={'140px'} - overflow={'auto'} - _hover={{ - '& .mask': { - display: 'block' - } - }} + px={3} + whiteSpace={'wrap'} + ml={1} > - {getValues('qaPrompt')} + {t('dataset:pdf_enhance_parse_price', { + price: feConfigs.customPdfParsePrice || 0 + })} + </MyTag> + )} + </HStack> + )} + </Flex> + </AccordionPanel> + </AccordionItem> + )} - <Box - display={'none'} - className="mask" - position={'absolute'} - top={0} - right={0} - bottom={0} - left={0} - background={ - 'linear-gradient(182deg, rgba(255, 255, 255, 0.00) 1.76%, #FFF 84.07%)' - } - > - <Button - size="xs" - variant={'whiteBase'} - leftIcon={<MyIcon name={'edit'} w={'13px'} />} - color={'black'} - position={'absolute'} - right={2} - bottom={2} - onClick={onOpenCustomPrompt} + <AccordionItem mt={4} border={'none'}> + <Title title={t('dataset:import_data_process_setting')} /> + + <AccordionPanel p={2}> + <Box mt={2}> + <Box fontSize={'sm'} mb={2} color={'myGray.600'}> + {t('dataset:training_mode')} + </Box> + <LeftRadio<DatasetCollectionDataProcessModeEnum> + list={trainingModeList} + px={3} + py={2.5} + value={trainingType} + onChange={(e) => { + setValue('trainingType', e); + }} + defaultBg="white" + activeBg="white" + gridTemplateColumns={'repeat(2, 1fr)'} + /> + </Box> + {trainingType === DatasetCollectionDataProcessModeEnum.chunk && feConfigs?.isPlus && ( + <Box mt={6}> + <Box fontSize={'sm'} mb={2} color={'myGray.600'}> + {t('dataset:enhanced_indexes')} + </Box> + <HStack gap={[3, 7]}> + <HStack flex={'1'} spacing={1}> + <Checkbox {...register('autoIndexes')}> + <FormLabel>{t('dataset:auto_indexes')}</FormLabel> + </Checkbox> + <QuestionTip label={t('dataset:auto_indexes_tips')} /> + </HStack> + <HStack flex={'1'} spacing={1}> + <MyTooltip + label={!datasetDetail?.vlmModel ? t('common:error_vlm_not_config') : ''} + > + <Checkbox isDisabled={!datasetDetail?.vlmModel} {...register('imageIndex')}> + <FormLabel>{t('dataset:image_auto_parse')}</FormLabel> + </Checkbox> + </MyTooltip> + <QuestionTip label={t('dataset:image_auto_parse_tips')} /> + </HStack> + </HStack> + </Box> + )} + <Box mt={6}> + <Box fontSize={'sm'} mb={2} color={'myGray.600'}> + {t('dataset:params_setting')} + </Box> + <LeftRadio<ChunkSettingModeEnum> + list={[ + { + title: t('dataset:default_params'), + desc: t('dataset:default_params_desc'), + value: ChunkSettingModeEnum.auto + }, + { + title: t('dataset:custom_data_process_params'), + desc: t('dataset:custom_data_process_params_desc'), + value: ChunkSettingModeEnum.custom, + children: chunkSettingMode === ChunkSettingModeEnum.custom && ( + <Box mt={5}> + <Box> + <Flex alignItems={'center'}> + <Box>{t('dataset:ideal_chunk_length')}</Box> + <QuestionTip label={t('dataset:ideal_chunk_length_tips')} /> + </Flex> + <Box + mt={1} + css={{ + '& > span': { + display: 'block' + } + }} > - {t('common:core.dataset.import.Custom prompt')} - </Button> + <MyTooltip + label={t('common:core.dataset.import.Chunk Range', { + min: minChunkSize, + max: maxChunkSize + })} + > + <MyNumberInput + register={register} + name={chunkSizeField} + min={minChunkSize} + max={maxChunkSize} + size={'sm'} + step={100} + /> + </MyTooltip> + </Box> </Box> + + <Box mt={3}> + <Box> + {t('common:core.dataset.import.Custom split char')} + <QuestionTip + label={t('common:core.dataset.import.Custom split char Tips')} + /> + </Box> + <Box mt={1}> + <Input + size={'sm'} + bg={'myGray.50'} + defaultValue={''} + placeholder="\n;======;==SPLIT==" + {...register('customSplitChar')} + /> + </Box> + </Box> + + {showQAPromptInput && ( + <Box mt={3}> + <Box>{t('common:core.dataset.collection.QA Prompt')}</Box> + <Box + position={'relative'} + py={2} + px={3} + bg={'myGray.50'} + fontSize={'xs'} + whiteSpace={'pre-wrap'} + border={'1px'} + borderColor={'borderColor.base'} + borderRadius={'md'} + maxH={'140px'} + overflow={'auto'} + _hover={{ + '& .mask': { + display: 'block' + } + }} + > + {getValues('qaPrompt')} + + <Box + display={'none'} + className="mask" + position={'absolute'} + top={0} + right={0} + bottom={0} + left={0} + background={ + 'linear-gradient(182deg, rgba(255, 255, 255, 0.00) 1.76%, #FFF 84.07%)' + } + > + <Button + size="xs" + variant={'whiteBase'} + leftIcon={<MyIcon name={'edit'} w={'13px'} />} + color={'black'} + position={'absolute'} + right={2} + bottom={2} + onClick={onOpenCustomPrompt} + > + {t('common:core.dataset.import.Custom prompt')} + </Button> + </Box> + </Box> + </Box> + )} </Box> - </Box> - )} - </Box> - ) - } - ]} - px={3} - py={3} - defaultBg="white" - activeBg="white" - value={way} - w={'100%'} - onChange={(e) => { - setValue('way', e); - }} - ></LeftRadio> - </Box> + ) + } + ]} + gridGap={3} + px={3} + py={3} + defaultBg="white" + activeBg="white" + value={chunkSettingMode} + w={'100%'} + onChange={(e) => { + setValue('chunkSettingMode', e); + }} + /> + </Box> + </AccordionPanel> + </AccordionItem> - {feConfigs?.show_pay && ( - <Box mt={5} pl={[0, '100px']} gap={3}> - <MyTag colorSchema={'gray'} py={1.5} borderRadius={'md'} px={3} whiteSpace={'wrap'}> - {priceTip} - </MyTag> - </Box> - )} + {/* <AccordionItem mt={4} border={'none'}> + <Title title={t('dataset:import_model_config')} /> + <AccordionPanel p={2} fontSize={'sm'}> + <Box> + <Box>{t('common:core.ai.model.Dataset Agent Model')}</Box> + <Box mt={1}> + <AIModelSelector + w={'100%'} + value={llmModel} + list={datasetModelList.map((item) => ({ + label: item.name, + value: item.model + }))} + onchange={(e) => { + setValue('llmModel', e); + }} + /> + </Box> + </Box> + <Box pt={5}> + <Box>{t('dataset:vllm_model')}</Box> + <Box mt={1}> + <AIModelSelector + w={'100%'} + value={vlmModel} + list={vllmModelList.map((item) => ({ + label: item.name, + value: item.model + }))} + onchange={(e) => { + setValue('vlmModel', e); + }} + /> + </Box> + </Box> + </AccordionPanel> + </AccordionItem> */} - <Flex mt={5} gap={3} justifyContent={'flex-end'}> - <Button - onClick={() => { - goToNext(); - }} - > - {t('common:common.Next Step')} - </Button> - </Flex> - </Box> - <Box flex={'1 0 0'} w={['auto', '0']} h={['auto', '100%']} pl={[0, 3]}> - <Preview showPreviewChunks={showPreviewChunks} /> + <Flex mt={5} gap={3} justifyContent={'flex-end'}> + <Button + onClick={() => { + goToNext(); + }} + > + {t('common:common.Next Step')} + </Button> + </Flex> + </Accordion> </Box> {isOpenCustomPrompt && ( @@ -273,7 +379,7 @@ function DataProcess({ showPreviewChunks = true }: { showPreviewChunks: boolean onClose={onCloseCustomPrompt} /> )} - </Box> + </> ); } diff --git a/projects/app/src/pageComponents/dataset/detail/Import/commonProgress/PreviewData.tsx b/projects/app/src/pageComponents/dataset/detail/Import/commonProgress/PreviewData.tsx index 892ffc25f4da..1b2ce5c23f2d 100644 --- a/projects/app/src/pageComponents/dataset/detail/Import/commonProgress/PreviewData.tsx +++ b/projects/app/src/pageComponents/dataset/detail/Import/commonProgress/PreviewData.tsx @@ -1,19 +1,162 @@ -import React from 'react'; -import Preview from '../components/Preview'; -import { Box, Button, Flex } from '@chakra-ui/react'; +import React, { useState } from 'react'; +import { Box, Button, Flex, HStack } from '@chakra-ui/react'; import { useTranslation } from 'next-i18next'; import { useContextSelector } from 'use-context-selector'; import { DatasetImportContext } from '../Context'; +import MyIcon from '@fastgpt/web/components/common/Icon'; +import FormLabel from '@fastgpt/web/components/common/MyBox/FormLabel'; +import EmptyTip from '@fastgpt/web/components/common/EmptyTip'; +import { useRequest2 } from '@fastgpt/web/hooks/useRequest'; +import { ImportDataSourceEnum } from '@fastgpt/global/core/dataset/constants'; +import { splitText2Chunks } from '@fastgpt/global/common/string/textSplitter'; +import { getPreviewChunks } from '@/web/core/dataset/api'; +import { ImportSourceItemType } from '@/web/core/dataset/type'; +import { getPreviewSourceReadType } from '../utils'; +import { DatasetPageContext } from '@/web/core/dataset/context/datasetPageContext'; +import MyBox from '@fastgpt/web/components/common/MyBox'; +import Markdown from '@/components/Markdown'; +import { useToast } from '@fastgpt/web/hooks/useToast'; -const PreviewData = ({ showPreviewChunks }: { showPreviewChunks: boolean }) => { +const PreviewData = () => { const { t } = useTranslation(); + const { toast } = useToast(); const goToNext = useContextSelector(DatasetImportContext, (v) => v.goToNext); + const datasetId = useContextSelector(DatasetPageContext, (v) => v.datasetId); + + const sources = useContextSelector(DatasetImportContext, (v) => v.sources); + const importSource = useContextSelector(DatasetImportContext, (v) => v.importSource); + const chunkSize = useContextSelector(DatasetImportContext, (v) => v.chunkSize); + const chunkOverlapRatio = useContextSelector(DatasetImportContext, (v) => v.chunkOverlapRatio); + const processParamsForm = useContextSelector(DatasetImportContext, (v) => v.processParamsForm); + + const [previewFile, setPreviewFile] = useState<ImportSourceItemType>(); + + const { data = [], loading: isLoading } = useRequest2( + async () => { + if (!previewFile) return; + if (importSource === ImportDataSourceEnum.fileCustom) { + const customSplitChar = processParamsForm.getValues('customSplitChar'); + const { chunks } = splitText2Chunks({ + text: previewFile.rawText || '', + chunkLen: chunkSize, + overlapRatio: chunkOverlapRatio, + customReg: customSplitChar ? [customSplitChar] : [] + }); + return chunks.map((chunk) => ({ + q: chunk, + a: '' + })); + } + + return getPreviewChunks({ + datasetId, + type: getPreviewSourceReadType(previewFile), + sourceId: + previewFile.dbFileId || + previewFile.link || + previewFile.externalFileUrl || + previewFile.apiFileId || + '', + + customPdfParse: processParamsForm.getValues('customPdfParse'), + + chunkSize, + overlapRatio: chunkOverlapRatio, + customSplitChar: processParamsForm.getValues('customSplitChar'), + + selector: processParamsForm.getValues('webSelector'), + isQAImport: importSource === ImportDataSourceEnum.csvTable, + externalFileId: previewFile.externalFileId + }); + }, + { + refreshDeps: [previewFile], + manual: false, + onSuccess(result) { + if (!previewFile) return; + if (!result || result.length === 0) { + toast({ + title: t('dataset:preview_chunk_empty'), + status: 'error' + }); + } + } + } + ); + return ( <Flex flexDirection={'column'} h={'100%'}> - <Box flex={'1 0 0 '}> - <Preview showPreviewChunks={showPreviewChunks} /> - </Box> + <Flex flex={'1 0 0'} border={'base'} borderRadius={'md'}> + <Flex flexDirection={'column'} flex={'1 0 0'} borderRight={'base'}> + <FormLabel fontSize={'md'} py={4} px={5} borderBottom={'base'}> + {t('dataset:file_list')} + </FormLabel> + <Box flex={'1 0 0'} overflowY={'auto'} px={5} py={3}> + {sources.map((source) => ( + <HStack + key={source.id} + bg={'myGray.50'} + p={4} + borderRadius={'md'} + borderWidth={'1px'} + borderColor={'transparent'} + cursor={'pointer'} + _hover={{ + borderColor: 'primary.300' + }} + {...(previewFile?.id === source.id && { + borderColor: 'primary.500 !important', + bg: 'primary.50 !important' + })} + _notLast={{ mb: 3 }} + onClick={() => setPreviewFile(source)} + > + <MyIcon name={source.icon as any} w={'1.25rem'} /> + <Box ml={1} flex={'1 0 0'} wordBreak={'break-all'} fontSize={'sm'}> + {source.sourceName} + </Box> + </HStack> + ))} + </Box> + </Flex> + <Flex flexDirection={'column'} flex={'1 0 0'}> + <Flex py={4} px={5} borderBottom={'base'} justifyContent={'space-between'}> + <FormLabel fontSize={'md'}>{t('dataset:preview_chunk')}</FormLabel> + <Box fontSize={'xs'} color={'myGray.500'}> + {t('dataset:preview_chunk_intro')} + </Box> + </Flex> + <MyBox isLoading={isLoading} flex={'1 0 0'} h={0}> + <Box h={'100%'} overflowY={'auto'} px={5} py={3}> + {previewFile ? ( + <> + {data.map((item, index) => ( + <Box + key={index} + fontSize={'sm'} + color={'myGray.600'} + _notLast={{ + mb: 3, + pb: 3, + borderBottom: 'base' + }} + _hover={{ + bg: 'myGray.100' + }} + > + <Markdown source={item.q} /> + <Markdown source={item.a} /> + </Box> + ))} + </> + ) : ( + <EmptyTip text={t('dataset:preview_chunk_not_selected')} /> + )} + </Box> + </MyBox> + </Flex> + </Flex> <Flex mt={2} justifyContent={'flex-end'}> <Button onClick={goToNext}>{t('common:common.Next Step')}</Button> </Flex> diff --git a/projects/app/src/pageComponents/dataset/detail/Import/commonProgress/Upload.tsx b/projects/app/src/pageComponents/dataset/detail/Import/commonProgress/Upload.tsx index e811a52540dd..489f9c0f21b7 100644 --- a/projects/app/src/pageComponents/dataset/detail/Import/commonProgress/Upload.tsx +++ b/projects/app/src/pageComponents/dataset/detail/Import/commonProgress/Upload.tsx @@ -14,7 +14,10 @@ import { IconButton, Tooltip } from '@chakra-ui/react'; -import { ImportDataSourceEnum } from '@fastgpt/global/core/dataset/constants'; +import { + DatasetCollectionDataProcessModeEnum, + ImportDataSourceEnum +} from '@fastgpt/global/core/dataset/constants'; import { useTranslation } from 'next-i18next'; import MyIcon from '@fastgpt/web/components/common/Icon'; import { useRequest2 } from '@fastgpt/web/hooks/useRequest'; @@ -34,6 +37,7 @@ import MyTag from '@fastgpt/web/components/common/Tag/index'; import { useContextSelector } from 'use-context-selector'; import { DatasetPageContext } from '@/web/core/dataset/context/datasetPageContext'; import { DatasetImportContext, type ImportFormType } from '../Context'; +import { ApiCreateDatasetCollectionParams } from '@fastgpt/global/core/dataset/api.d'; const Upload = () => { const { t } = useTranslation(); @@ -77,7 +81,7 @@ const Upload = () => { }, [waitingFilesCount, totalFilesCount, allFinished, t]); const { runAsync: startUpload, loading: isLoading } = useRequest2( - async ({ mode, customSplitChar, qaPrompt, webSelector }: ImportFormType) => { + async ({ trainingType, customSplitChar, qaPrompt, webSelector }: ImportFormType) => { if (sources.length === 0) return; const filterWaitingSources = sources.filter((item) => item.createStatus === 'waiting'); @@ -95,15 +99,21 @@ const Upload = () => { ); // create collection - const commonParams = { + const commonParams: ApiCreateDatasetCollectionParams & { + name: string; + } = { parentId, - trainingType: mode, datasetId: datasetDetail._id, + name: item.sourceName, + + customPdfParse: processParamsForm.getValues('customPdfParse'), + + trainingType, + imageIndex: processParamsForm.getValues('imageIndex'), + autoIndexes: processParamsForm.getValues('autoIndexes'), chunkSize, chunkSplitter: customSplitChar, - qaPrompt, - - name: item.sourceName + qaPrompt: trainingType === DatasetCollectionDataProcessModeEnum.qa ? qaPrompt : undefined }; if (importSource === ImportDataSourceEnum.reTraining) { const res = await postReTrainingDatasetFileCollection({ @@ -272,7 +282,7 @@ const Upload = () => { <Flex justifyContent={'flex-end'} mt={4}> <Button isLoading={isLoading} onClick={handleSubmit((data) => startUpload(data))}> {totalFilesCount > 0 && - `${t('common:core.dataset.import.Total files', { + `${t('dataset:total_num_files', { total: totalFilesCount })} | `} {buttonText} diff --git a/projects/app/src/pageComponents/dataset/detail/Import/components/Preview.tsx b/projects/app/src/pageComponents/dataset/detail/Import/components/Preview.tsx deleted file mode 100644 index 09ba8574208d..000000000000 --- a/projects/app/src/pageComponents/dataset/detail/Import/components/Preview.tsx +++ /dev/null @@ -1,102 +0,0 @@ -import React, { useState } from 'react'; -import { Box, Flex, Grid, IconButton } from '@chakra-ui/react'; -import MyIcon from '@fastgpt/web/components/common/Icon'; -import { useTranslation } from 'next-i18next'; - -import MyMenu from '@fastgpt/web/components/common/MyMenu'; -import { ImportSourceItemType } from '@/web/core/dataset/type'; -import dynamic from 'next/dynamic'; -import { useContextSelector } from 'use-context-selector'; -import { DatasetImportContext } from '../Context'; -const PreviewRawText = dynamic(() => import('./PreviewRawText')); -const PreviewChunks = dynamic(() => import('./PreviewChunks')); - -const Preview = ({ showPreviewChunks }: { showPreviewChunks: boolean }) => { - const { t } = useTranslation(); - - const { sources } = useContextSelector(DatasetImportContext, (v) => v); - const [previewRawTextSource, setPreviewRawTextSource] = useState<ImportSourceItemType>(); - const [previewChunkSource, setPreviewChunkSource] = useState<ImportSourceItemType>(); - - return ( - <Box h={'100%'} w={'100%'} display={['block', 'flex']} flexDirection={'column'}> - <Flex alignItems={'center'}> - <MyIcon name={'core/dataset/fileCollection'} w={'20px'} /> - <Box fontSize={'md'}>{t('common:core.dataset.import.Sources list')}</Box> - </Flex> - <Box mt={3} flex={'1 0 0'} h={['auto', 0]} width={'100%'} overflowY={'auto'}> - <Grid w={'100%'} gap={3} gridTemplateColumns={['1fr', '1fr', '1fr', '1fr', '1fr 1fr']}> - {sources.map((source) => ( - <Flex - key={source.id} - bg={'white'} - p={4} - borderRadius={'md'} - borderWidth={'1px'} - borderColor={'borderColor.low'} - boxShadow={'2'} - alignItems={'center'} - > - <MyIcon name={source.icon as any} w={['1rem', '1.25rem']} /> - <Box mx={1} flex={'1 0 0'} wordBreak={'break-all'} fontSize={'sm'}> - {source.sourceName} - </Box> - {showPreviewChunks && ( - <Box fontSize={'xs'} color={'myGray.600'}> - <MyMenu - Button={ - <IconButton - icon={<MyIcon name={'common/viewLight'} w={'14px'} p={2} />} - aria-label={''} - size={'sm'} - variant={'whitePrimary'} - /> - } - menuList={[ - { - children: [ - { - label: ( - <Flex alignItems={'center'}> - <MyIcon name={'core/dataset/fileCollection'} w={'14px'} mr={2} /> - {t('common:core.dataset.import.Preview raw text')} - </Flex> - ), - onClick: () => setPreviewRawTextSource(source) - }, - { - label: ( - <Flex alignItems={'center'}> - <MyIcon name={'core/dataset/splitLight'} w={'14px'} mr={2} /> - {t('common:core.dataset.import.Preview chunks')} - </Flex> - ), - onClick: () => setPreviewChunkSource(source) - } - ] - } - ]} - /> - </Box> - )} - </Flex> - ))} - </Grid> - </Box> - {!!previewRawTextSource && ( - <PreviewRawText - previewSource={previewRawTextSource} - onClose={() => setPreviewRawTextSource(undefined)} - /> - )} - {!!previewChunkSource && ( - <PreviewChunks - previewSource={previewChunkSource} - onClose={() => setPreviewChunkSource(undefined)} - /> - )} - </Box> - ); -}; - -export default React.memo(Preview); diff --git a/projects/app/src/pageComponents/dataset/detail/Import/components/PreviewRawText.tsx b/projects/app/src/pageComponents/dataset/detail/Import/components/PreviewRawText.tsx deleted file mode 100644 index 6eb01b693cf7..000000000000 --- a/projects/app/src/pageComponents/dataset/detail/Import/components/PreviewRawText.tsx +++ /dev/null @@ -1,78 +0,0 @@ -import React from 'react'; -import { Box } from '@chakra-ui/react'; -import { ImportSourceItemType } from '@/web/core/dataset/type'; -import { getPreviewFileContent } from '@/web/common/file/api'; -import MyRightDrawer from '@fastgpt/web/components/common/MyDrawer/MyRightDrawer'; -import { ImportDataSourceEnum } from '@fastgpt/global/core/dataset/constants'; -import { useToast } from '@fastgpt/web/hooks/useToast'; -import { getErrText } from '@fastgpt/global/common/error/utils'; -import { useContextSelector } from 'use-context-selector'; -import { DatasetImportContext } from '../Context'; -import { DatasetPageContext } from '@/web/core/dataset/context/datasetPageContext'; -import { useRequest2 } from '@fastgpt/web/hooks/useRequest'; -import { getPreviewSourceReadType } from '../utils'; - -const PreviewRawText = ({ - previewSource, - onClose -}: { - previewSource: ImportSourceItemType; - onClose: () => void; -}) => { - const { toast } = useToast(); - const { importSource, processParamsForm } = useContextSelector(DatasetImportContext, (v) => v); - const datasetId = useContextSelector(DatasetPageContext, (v) => v.datasetId); - - const { data, loading: isLoading } = useRequest2( - async () => { - if (importSource === ImportDataSourceEnum.fileCustom && previewSource.rawText) { - return { - previewContent: previewSource.rawText.slice(0, 3000) - }; - } - - return getPreviewFileContent({ - datasetId, - type: getPreviewSourceReadType(previewSource), - sourceId: - previewSource.dbFileId || - previewSource.link || - previewSource.externalFileUrl || - previewSource.apiFileId || - '', - - isQAImport: importSource === ImportDataSourceEnum.csvTable, - selector: processParamsForm.getValues('webSelector'), - externalFileId: previewSource.externalFileId - }); - }, - { - refreshDeps: [previewSource.dbFileId, previewSource.link, previewSource.externalFileUrl], - manual: false, - onError(err) { - toast({ - status: 'warning', - title: getErrText(err) - }); - } - } - ); - - const rawText = data?.previewContent || ''; - - return ( - <MyRightDrawer - onClose={onClose} - iconSrc={previewSource.icon} - title={previewSource.sourceName} - isLoading={isLoading} - px={0} - > - <Box whiteSpace={'pre-wrap'} overflowY={'auto'} px={5} fontSize={'sm'}> - {rawText} - </Box> - </MyRightDrawer> - ); -}; - -export default React.memo(PreviewRawText); diff --git a/projects/app/src/pageComponents/dataset/detail/Import/components/RenderFiles.tsx b/projects/app/src/pageComponents/dataset/detail/Import/components/RenderFiles.tsx index 473575560bfe..86aae27cba80 100644 --- a/projects/app/src/pageComponents/dataset/detail/Import/components/RenderFiles.tsx +++ b/projects/app/src/pageComponents/dataset/detail/Import/components/RenderFiles.tsx @@ -14,24 +14,17 @@ import { import { ImportSourceItemType } from '@/web/core/dataset/type.d'; import MyIcon from '@fastgpt/web/components/common/Icon'; import { useTranslation } from 'next-i18next'; -import MyTooltip from '@fastgpt/web/components/common/MyTooltip'; -import dynamic from 'next/dynamic'; import { useI18n } from '@/web/context/I18n'; -const PreviewRawText = dynamic(() => import('./PreviewRawText')); - export const RenderUploadFiles = ({ files, - setFiles, - showPreviewContent + setFiles }: { files: ImportSourceItemType[]; setFiles: React.Dispatch<React.SetStateAction<ImportSourceItemType[]>>; - showPreviewContent?: boolean; }) => { const { t } = useTranslation(); const { fileT } = useI18n(); - const [previewFile, setPreviewFile] = useState<ImportSourceItemType>(); return files.length > 0 ? ( <> @@ -84,18 +77,6 @@ export const RenderUploadFiles = ({ <Td> {!item.isUploading && ( <Flex alignItems={'center'} gap={4}> - {showPreviewContent && ( - <MyTooltip label={t('common:core.dataset.import.Preview raw text')}> - <IconButton - variant={'whitePrimary'} - size={'sm'} - icon={<MyIcon name={'common/viewLight'} w={'18px'} />} - aria-label={''} - onClick={() => setPreviewFile(item)} - /> - </MyTooltip> - )} - <IconButton variant={'grayDanger'} size={'sm'} @@ -113,9 +94,6 @@ export const RenderUploadFiles = ({ </Tbody> </Table> </TableContainer> - {!!previewFile && ( - <PreviewRawText previewSource={previewFile} onClose={() => setPreviewFile(undefined)} /> - )} </> ) : null; }; diff --git a/projects/app/src/pageComponents/dataset/detail/Import/diffSource/APIDataset.tsx b/projects/app/src/pageComponents/dataset/detail/Import/diffSource/APIDataset.tsx index e0cdff95ca54..dee1dd83fa04 100644 --- a/projects/app/src/pageComponents/dataset/detail/Import/diffSource/APIDataset.tsx +++ b/projects/app/src/pageComponents/dataset/detail/Import/diffSource/APIDataset.tsx @@ -28,7 +28,7 @@ const APIDatasetCollection = () => { return ( <> {activeStep === 0 && <CustomAPIFileInput />} - {activeStep === 1 && <DataProcess showPreviewChunks={true} />} + {activeStep === 1 && <DataProcess />} {activeStep === 2 && <Upload />} </> ); @@ -272,7 +272,7 @@ const CustomAPIFileInput = () => { onClick={onclickNext} > {selectFiles.length > 0 - ? `${t('common:core.dataset.import.Total files', { total: selectFiles.length })} | ` + ? `${t('dataset:total_num_files', { total: selectFiles.length })} | ` : ''} {t('common:common.Next Step')} </Button> diff --git a/projects/app/src/pageComponents/dataset/detail/Import/diffSource/ExternalFile.tsx b/projects/app/src/pageComponents/dataset/detail/Import/diffSource/ExternalFile.tsx index aef5bb934eb5..e1e29b00acf8 100644 --- a/projects/app/src/pageComponents/dataset/detail/Import/diffSource/ExternalFile.tsx +++ b/projects/app/src/pageComponents/dataset/detail/Import/diffSource/ExternalFile.tsx @@ -34,7 +34,7 @@ const ExternalFileCollection = () => { return ( <> {activeStep === 0 && <CustomLinkInput />} - {activeStep === 1 && <DataProcess showPreviewChunks={true} />} + {activeStep === 1 && <DataProcess />} {activeStep === 2 && <Upload />} </> ); diff --git a/projects/app/src/pageComponents/dataset/detail/Import/diffSource/FileCustomText.tsx b/projects/app/src/pageComponents/dataset/detail/Import/diffSource/FileCustomText.tsx index b40fd748978b..12c0c28de428 100644 --- a/projects/app/src/pageComponents/dataset/detail/Import/diffSource/FileCustomText.tsx +++ b/projects/app/src/pageComponents/dataset/detail/Import/diffSource/FileCustomText.tsx @@ -19,7 +19,7 @@ const CustomTet = () => { return ( <> {activeStep === 0 && <CustomTextInput />} - {activeStep === 1 && <DataProcess showPreviewChunks />} + {activeStep === 1 && <DataProcess />} {activeStep === 2 && <Upload />} </> ); diff --git a/projects/app/src/pageComponents/dataset/detail/Import/diffSource/FileLink.tsx b/projects/app/src/pageComponents/dataset/detail/Import/diffSource/FileLink.tsx index 11a5e4fe7140..b9f0e192ea00 100644 --- a/projects/app/src/pageComponents/dataset/detail/Import/diffSource/FileLink.tsx +++ b/projects/app/src/pageComponents/dataset/detail/Import/diffSource/FileLink.tsx @@ -23,7 +23,7 @@ const LinkCollection = () => { return ( <> {activeStep === 0 && <CustomLinkImport />} - {activeStep === 1 && <DataProcess showPreviewChunks />} + {activeStep === 1 && <DataProcess />} {activeStep === 2 && <Upload />} </> ); diff --git a/projects/app/src/pageComponents/dataset/detail/Import/diffSource/FileLocal.tsx b/projects/app/src/pageComponents/dataset/detail/Import/diffSource/FileLocal.tsx index aa162f4a970e..605511c80537 100644 --- a/projects/app/src/pageComponents/dataset/detail/Import/diffSource/FileLocal.tsx +++ b/projects/app/src/pageComponents/dataset/detail/Import/diffSource/FileLocal.tsx @@ -10,9 +10,8 @@ import { RenderUploadFiles } from '../components/RenderFiles'; import { useContextSelector } from 'use-context-selector'; import { DatasetImportContext } from '../Context'; -const DataProcess = dynamic(() => import('../commonProgress/DataProcess'), { - loading: () => <Loading fixed={false} /> -}); +const DataProcess = dynamic(() => import('../commonProgress/DataProcess')); +const PreviewData = dynamic(() => import('../commonProgress/PreviewData')); const Upload = dynamic(() => import('../commonProgress/Upload')); const fileType = '.txt, .docx, .csv, .xlsx, .pdf, .md, .html, .pptx'; @@ -23,8 +22,9 @@ const FileLocal = () => { return ( <> {activeStep === 0 && <SelectFile />} - {activeStep === 1 && <DataProcess showPreviewChunks />} - {activeStep === 2 && <Upload />} + {activeStep === 1 && <DataProcess />} + {activeStep === 2 && <PreviewData />} + {activeStep === 3 && <Upload />} </> ); }; @@ -64,12 +64,12 @@ const SelectFile = React.memo(function SelectFile() { /> {/* render files */} - <RenderUploadFiles files={selectFiles} setFiles={setSelectFiles} showPreviewContent /> + <RenderUploadFiles files={selectFiles} setFiles={setSelectFiles} /> <Box textAlign={'right'} mt={5}> <Button isDisabled={successFiles.length === 0 || uploading} onClick={onclickNext}> {selectFiles.length > 0 - ? `${t('core.dataset.import.Total files', { total: selectFiles.length })} | ` + ? `${t('dataset:total_num_files', { total: selectFiles.length })} | ` : ''} {t('common:common.Next Step')} </Button> diff --git a/projects/app/src/pageComponents/dataset/detail/Import/diffSource/ReTraining.tsx b/projects/app/src/pageComponents/dataset/detail/Import/diffSource/ReTraining.tsx index 2c6e6cc4e86f..ba7d56fa2e10 100644 --- a/projects/app/src/pageComponents/dataset/detail/Import/diffSource/ReTraining.tsx +++ b/projects/app/src/pageComponents/dataset/detail/Import/diffSource/ReTraining.tsx @@ -8,10 +8,13 @@ import { useRouter } from 'next/router'; import { useRequest2 } from '@fastgpt/web/hooks/useRequest'; import { getDatasetCollectionById } from '@/web/core/dataset/api'; import MyBox from '@fastgpt/web/components/common/MyBox'; -import { ImportProcessWayEnum } from '@/web/core/dataset/constants'; +import { ChunkSettingModeEnum } from '@/web/core/dataset/constants'; import { getCollectionIcon } from '@fastgpt/global/core/dataset/utils'; +import { DatasetPageContext } from '@/web/core/dataset/context/datasetPageContext'; +import { Box } from '@chakra-ui/react'; const Upload = dynamic(() => import('../commonProgress/Upload')); +const PreviewData = dynamic(() => import('../commonProgress/PreviewData')); const ReTraining = () => { const router = useRouter(); @@ -20,6 +23,7 @@ const ReTraining = () => { collectionId: string; }; + const datasetDetail = useContextSelector(DatasetPageContext, (v) => v.datasetDetail); const activeStep = useContextSelector(DatasetImportContext, (v) => v.activeStep); const setSources = useContextSelector(DatasetImportContext, (v) => v.setSources); const processParamsForm = useContextSelector(DatasetImportContext, (v) => v.processParamsForm); @@ -43,8 +47,12 @@ const ReTraining = () => { } ]); processParamsForm.reset({ - mode: collection.trainingType, - way: ImportProcessWayEnum.auto, + customPdfParse: collection.customPdfParse, + trainingType: collection.trainingType, + imageIndex: collection.imageIndex, + autoIndexes: collection.autoIndexes, + + chunkSettingMode: ChunkSettingModeEnum.auto, embeddingChunkSize: collection.chunkSize, qaChunkSize: collection.chunkSize, customSplitChar: collection.chunkSplitter, @@ -55,9 +63,12 @@ const ReTraining = () => { }); return ( - <MyBox isLoading={loading} h={'100%'} overflow={'auto'}> - {activeStep === 0 && <DataProcess showPreviewChunks={true} />} - {activeStep === 1 && <Upload />} + <MyBox isLoading={loading} h={'100%'}> + <Box h={'100%'} overflow={'auto'}> + {activeStep === 0 && <DataProcess />} + {activeStep === 1 && <PreviewData />} + {activeStep === 2 && <Upload />} + </Box> </MyBox> ); }; diff --git a/projects/app/src/pageComponents/dataset/detail/Import/diffSource/TableLocal.tsx b/projects/app/src/pageComponents/dataset/detail/Import/diffSource/TableLocal.tsx index 83cc4b7d016f..2b878ff6e209 100644 --- a/projects/app/src/pageComponents/dataset/detail/Import/diffSource/TableLocal.tsx +++ b/projects/app/src/pageComponents/dataset/detail/Import/diffSource/TableLocal.tsx @@ -21,7 +21,7 @@ const FileLocal = () => { return ( <> {activeStep === 0 && <SelectFile />} - {activeStep === 1 && <PreviewData showPreviewChunks />} + {activeStep === 1 && <PreviewData />} {activeStep === 2 && <Upload />} </> ); @@ -91,7 +91,7 @@ const SelectFile = React.memo(function SelectFile() { }} > {selectFiles.length > 0 - ? `${t('core.dataset.import.Total files', { total: selectFiles.length })} | ` + ? `${t('dataset:total_num_files', { total: selectFiles.length })} | ` : ''} {t('common:common.Next Step')} </Button> diff --git a/projects/app/src/pageComponents/dataset/detail/Info/index.tsx b/projects/app/src/pageComponents/dataset/detail/Info/index.tsx index 89dbe73a78fc..375e07abd43f 100644 --- a/projects/app/src/pageComponents/dataset/detail/Info/index.tsx +++ b/projects/app/src/pageComponents/dataset/detail/Info/index.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from 'react'; +import React, { useEffect, useMemo, useState } from 'react'; import { Box, Flex, Switch, Input } from '@chakra-ui/react'; import { useConfirm } from '@fastgpt/web/hooks/useConfirm'; import { useForm } from 'react-hook-form'; @@ -37,6 +37,8 @@ const Info = ({ datasetId }: { datasetId: string }) => { const { t } = useTranslation(); const { datasetDetail, loadDatasetDetail, updateDataset, rebuildingCount, trainingCount } = useContextSelector(DatasetPageContext, (v) => v); + const { feConfigs, datasetModelList, embeddingModelList, getVlmModelList } = useSystemStore(); + const [editedDataset, setEditedDataset] = useState<EditResourceInfoFormType>(); const [editedAPIDataset, setEditedAPIDataset] = useState<EditAPIDatasetInfoFormType>(); const refetchDatasetTraining = useContextSelector( @@ -50,7 +52,9 @@ const Info = ({ datasetId }: { datasetId: string }) => { const vectorModel = watch('vectorModel'); const agentModel = watch('agentModel'); - const { feConfigs, datasetModelList, embeddingModelList } = useSystemStore(); + const vllmModelList = useMemo(() => getVlmModelList(), [getVlmModelList]); + const vlmModel = watch('vlmModel'); + const { ConfirmModal: ConfirmDelModal } = useConfirm({ content: t('common:core.dataset.Delete Confirm'), type: 'delete' @@ -69,7 +73,8 @@ const Info = ({ datasetId }: { datasetId: string }) => { (data: DatasetItemType) => { return updateDataset({ id: datasetId, - agentModel: data.agentModel, + agentModel: data.agentModel?.model, + vlmModel: data.vlmModel?.model, externalReadUrl: data.externalReadUrl }); }, @@ -225,6 +230,31 @@ const Info = ({ datasetId }: { datasetId: string }) => { </Box> </Box> + {feConfigs?.isPlus && ( + <Box pt={5}> + <FormLabel fontSize={'mini'} fontWeight={'500'}> + {t('dataset:vllm_model')} + </FormLabel> + <Box pt={2}> + <AIModelSelector + w={'100%'} + value={vlmModel?.model} + list={vllmModelList.map((item) => ({ + label: item.name, + value: item.model + }))} + fontSize={'mini'} + onchange={(e) => { + const vlmModel = vllmModelList.find((item) => item.model === e); + if (!vlmModel) return; + setValue('vlmModel', vlmModel); + return handleSubmit((data) => onSave({ ...data, vlmModel }))(); + }} + /> + </Box> + </Box> + )} + {feConfigs?.isPlus && ( <Flex alignItems={'center'} pt={5}> <FormLabel fontSize={'mini'} fontWeight={'500'}> diff --git a/projects/app/src/pageComponents/dataset/detail/InputDataModal.tsx b/projects/app/src/pageComponents/dataset/detail/InputDataModal.tsx index e39373e6f101..ce69dea0128f 100644 --- a/projects/app/src/pageComponents/dataset/detail/InputDataModal.tsx +++ b/projects/app/src/pageComponents/dataset/detail/InputDataModal.tsx @@ -1,9 +1,7 @@ import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; -import { Box, Flex, Button, Textarea, useTheme, Grid, HStack } from '@chakra-ui/react'; +import { Box, Flex, Button, Textarea } from '@chakra-ui/react'; import { - Control, FieldArrayWithId, - UseFieldArrayAppend, UseFieldArrayRemove, UseFormRegister, useFieldArray, @@ -12,7 +10,6 @@ import { import { postInsertData2Dataset, putDatasetDataById, - delOneDatasetDataById, getDatasetCollectionById, getDatasetDataItemById } from '@/web/core/dataset/api'; @@ -22,9 +19,8 @@ import MyModal from '@fastgpt/web/components/common/MyModal'; import MyTooltip from '@fastgpt/web/components/common/MyTooltip'; import { useQuery } from '@tanstack/react-query'; import { useTranslation } from 'next-i18next'; -import { useRequest, useRequest2 } from '@fastgpt/web/hooks/useRequest'; -import { useConfirm } from '@fastgpt/web/hooks/useConfirm'; -import { getDefaultIndex, getSourceNameIcon } from '@fastgpt/global/core/dataset/utils'; +import { useRequest2 } from '@fastgpt/web/hooks/useRequest'; +import { getSourceNameIcon } from '@fastgpt/global/core/dataset/utils'; import { DatasetDataIndexItemType } from '@fastgpt/global/core/dataset/type'; import DeleteIcon from '@fastgpt/web/components/common/Icon/delete'; import { defaultCollectionDetail } from '@/web/core/dataset/constants'; @@ -33,9 +29,12 @@ import MyBox from '@fastgpt/web/components/common/MyBox'; import { getErrText } from '@fastgpt/global/common/error/utils'; import { useSystemStore } from '@/web/common/system/useSystemStore'; import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip'; -import { useSystem } from '@fastgpt/web/hooks/useSystem'; import LightRowTabs from '@fastgpt/web/components/common/Tabs/LightRowTabs'; import styles from './styles.module.scss'; +import { + DatasetDataIndexTypeEnum, + getDatasetIndexMapData +} from '@fastgpt/global/core/dataset/data/constants'; export type InputDataType = { q: string; @@ -64,11 +63,10 @@ const InputDataModal = ({ onSuccess: (data: InputDataType & { dataId: string }) => void; }) => { const { t } = useTranslation(); - const theme = useTheme(); const { toast } = useToast(); const [currentTab, setCurrentTab] = useState(TabEnum.content); const { embeddingModelList, defaultModels } = useSystemStore(); - const { isPc } = useSystem(); + const { register, handleSubmit, reset, control } = useForm<InputDataType>(); const { fields: indexes, @@ -114,11 +112,6 @@ const InputDataModal = ({ } ]; - const { ConfirmModal, openConfirm } = useConfirm({ - content: t('common:dataset.data.Delete Tip'), - type: 'delete' - }); - const { data: collection = defaultCollectionDetail } = useQuery( ['loadCollectionId', collectionId], () => { @@ -165,8 +158,8 @@ const InputDataModal = ({ }, [collection.dataset.vectorModel, defaultModels.embedding, embeddingModelList]); // import new data - const { mutate: sureImportData, isLoading: isImporting } = useRequest({ - mutationFn: async (e: InputDataType) => { + const { runAsync: sureImportData, loading: isImporting } = useRequest2( + async (e: InputDataType) => { if (!e.q) { setCurrentTab(TabEnum.content); return Promise.reject(t('common:dataset.data.input is empty')); @@ -183,12 +176,8 @@ const InputDataModal = ({ collectionId: collection._id, q: e.q, a: e.a, - // remove dataId - indexes: - e.indexes?.map((index) => ({ - ...index, - dataId: undefined - })) || [] + // Contains no default index + indexes: e.indexes }); return { @@ -196,18 +185,20 @@ const InputDataModal = ({ dataId }; }, - successToast: t('common:dataset.data.Input Success Tip'), - onSuccess(e) { - reset({ - ...e, - q: '', - a: '', - indexes: [] - }); - onSuccess(e); - }, - errorToast: t('common:common.error.unKnow') - }); + { + successToast: t('common:dataset.data.Input Success Tip'), + onSuccess(e) { + reset({ + ...e, + q: '', + a: '', + indexes: [] + }); + onSuccess(e); + }, + errorToast: t('common:common.error.unKnow') + } + ); // update const { runAsync: onUpdateData, loading: isUpdating } = useRequest2( @@ -218,10 +209,7 @@ const InputDataModal = ({ await putDatasetDataById({ dataId, ...e, - indexes: - e.indexes?.map((index) => - index.defaultIndex ? getDefaultIndex({ q: e.q, a: e.a, dataId: index.dataId }) : index - ) || [] + indexes: e.indexes }); return { @@ -244,6 +232,7 @@ const InputDataModal = ({ () => getSourceNameIcon({ sourceName: collection.sourceName, sourceId: collection.sourceId }), [collection] ); + return ( <MyModal isOpen={true} @@ -296,9 +285,8 @@ const InputDataModal = ({ p={0} onClick={() => appendIndexes({ - defaultIndex: false, - text: '', - dataId: `${Date.now()}` + type: DatasetDataIndexTypeEnum.custom, + text: '' }) } > @@ -315,7 +303,6 @@ const InputDataModal = ({ <DataIndex register={register} maxToken={maxToken} - appendIndexes={appendIndexes} removeIndexes={removeIndexes} indexes={indexes} /> @@ -337,7 +324,6 @@ const InputDataModal = ({ </MyTooltip> </Flex> </MyBox> - <ConfirmModal /> </MyModal> ); }; @@ -424,13 +410,11 @@ const DataIndex = ({ maxToken, register, indexes, - appendIndexes, removeIndexes }: { maxToken: number; register: UseFormRegister<InputDataType>; indexes: FieldArrayWithId<InputDataType, 'indexes', 'id'>[]; - appendIndexes: UseFieldArrayAppend<InputDataType, 'indexes'>; removeIndexes: UseFieldArrayRemove; }) => { const { t } = useTranslation(); @@ -438,52 +422,41 @@ const DataIndex = ({ return ( <> <Flex mt={3} gap={3} flexDir={'column'}> - <Box - p={4} - borderRadius={'md'} - border={'1.5px solid var(--light-fastgpt-primary-opacity-01, rgba(51, 112, 255, 0.10))'} - bg={'primary.50'} - > - <Flex mb={2}> - <Box flex={1} fontWeight={'medium'} fontSize={'sm'} color={'primary.700'}> - {t('common:dataset.data.Default Index')} - </Box> - </Flex> - <Box fontSize={'sm'} fontWeight={'medium'} color={'myGray.600'}> - {t('common:core.dataset.data.Default Index Tip')} - </Box> - </Box> {indexes?.map((index, i) => { + const data = getDatasetIndexMapData(index.type); return ( - !index.defaultIndex && ( - <Box - key={index.dataId || i} - p={4} - borderRadius={'md'} - border={'1.5px solid var(--Gray-Modern-200, #E8EBF0)'} - bg={'myGray.25'} - _hover={{ - '& .delete': { - display: 'block' - } - }} - > - <Flex mb={2}> - <Box flex={1} fontWeight={'medium'} fontSize={'sm'} color={'myGray.900'}> - {t('dataset.data.Custom Index Number', { number: i })} - </Box> + <Box + key={index.dataId || i} + p={4} + borderRadius={'md'} + border={'1.5px solid var(--Gray-Modern-200, #E8EBF0)'} + bg={'myGray.25'} + _hover={{ + '& .delete': { + display: 'block' + } + }} + > + <Flex mb={2}> + <Box flex={1} fontWeight={'medium'} fontSize={'sm'} color={'myGray.900'}> + {t(data.label)} + </Box> + {index.type !== 'default' && ( <DeleteIcon onClick={() => { - if (indexes.length <= 1) { - appendIndexes(getDefaultIndex({ dataId: `${Date.now()}` })); - } removeIndexes(i); }} /> - </Flex> - <DataIndexTextArea index={i} maxToken={maxToken} register={register} /> - </Box> - ) + )} + </Flex> + <DataIndexTextArea + disabled={index.type === 'default'} + index={i} + value={index.text} + maxToken={maxToken} + register={register} + /> + </Box> ); })} </Flex> @@ -491,14 +464,19 @@ const DataIndex = ({ ); }; +const textareaMinH = '40px'; const DataIndexTextArea = ({ + value, index, maxToken, - register + register, + disabled }: { + value: string; index: number; maxToken: number; register: UseFormRegister<InputDataType>; + disabled?: boolean; }) => { const { t } = useTranslation(); const TextareaDom = useRef<HTMLTextAreaElement | null>(null); @@ -509,7 +487,7 @@ const DataIndexTextArea = ({ onChange: onTextChange, onBlur } = register(`indexes.${index}.text`, { required: true }); - const textareaMinH = '40px'; + useEffect(() => { if (TextareaDom.current) { TextareaDom.current.style.height = textareaMinH; @@ -522,7 +500,12 @@ const DataIndexTextArea = ({ e.target.style.height = `${e.target.scrollHeight + 5}px`; } }, []); - return ( + + return disabled ? ( + <Box fontSize={'sm'} color={'myGray.500'} whiteSpace={'pre-wrap'}> + {value} + </Box> + ) : ( <Textarea maxLength={maxToken} borderColor={'transparent'} diff --git a/projects/app/src/pageComponents/dataset/detail/MetaDataCard.tsx b/projects/app/src/pageComponents/dataset/detail/MetaDataCard.tsx index cc339936c4a5..43059c4c7a0d 100644 --- a/projects/app/src/pageComponents/dataset/detail/MetaDataCard.tsx +++ b/projects/app/src/pageComponents/dataset/detail/MetaDataCard.tsx @@ -7,7 +7,10 @@ import { useRouter } from 'next/router'; import MyBox from '@fastgpt/web/components/common/MyBox'; import { formatFileSize } from '@fastgpt/global/common/file/tools'; import { formatTime2YMDHM } from '@fastgpt/global/common/string/time'; -import { DatasetCollectionTypeMap, TrainingTypeMap } from '@fastgpt/global/core/dataset/constants'; +import { + DatasetCollectionDataProcessModeMap, + DatasetCollectionTypeMap +} from '@fastgpt/global/core/dataset/constants'; import { getCollectionSourceAndOpen } from '@/web/core/dataset/hooks/readCollectionSource'; import MyIcon from '@fastgpt/web/components/common/Icon'; @@ -61,13 +64,25 @@ const MetaDataCard = ({ datasetId }: { datasetId: string }) => { label: t('common:core.dataset.collection.metadata.Updatetime'), value: formatTime2YMDHM(collection.updateTime) }, + { + label: t('dataset:collection_metadata_custom_pdf_parse'), + value: collection.customPdfParse ? 'Yes' : 'No' + }, { label: t('common:core.dataset.collection.metadata.Raw text length'), value: collection.rawTextLength ?? '-' }, { - label: t('dataset:collection.Training type'), - value: t(TrainingTypeMap[collection.trainingType]?.label as any) + label: t('dataset:collection_metadata_image_parse'), + value: collection.imageIndex ? 'Yes' : 'No' + }, + { + label: t('dataset:auto_indexes'), + value: collection.autoIndexes ? 'Yes' : 'No' + }, + { + label: t('dataset:collection.training_type'), + value: t(DatasetCollectionDataProcessModeMap[collection.trainingType]?.label as any) }, { label: t('common:core.dataset.collection.metadata.Chunk Size'), @@ -99,8 +114,8 @@ const MetaDataCard = ({ datasetId }: { datasetId: string }) => { <Box fontSize={'md'} pb={4}> {t('common:core.dataset.collection.metadata.metadata')} </Box> - <Flex mb={4} wordBreak={'break-all'} fontSize={'sm'}> - <Box color={'myGray.500'} flex={'0 0 70px'}> + <Flex mb={3} wordBreak={'break-all'} fontSize={'sm'}> + <Box color={'myGray.500'} flex={'0 0 90px'}> {t('common:core.dataset.collection.id')}: </Box> <Box>{collection?._id}</Box> @@ -109,8 +124,8 @@ const MetaDataCard = ({ datasetId }: { datasetId: string }) => { (item, i) => item.label && item.value && ( - <Flex key={i} alignItems={'center'} mb={4} wordBreak={'break-all'} fontSize={'sm'}> - <Box color={'myGray.500'} flex={'0 0 70px'}> + <Flex key={i} alignItems={'center'} mb={3} wordBreak={'break-all'} fontSize={'sm'}> + <Box color={'myGray.500'} flex={'0 0 90px'}> {item.label} </Box> <Box>{item.value}</Box> diff --git a/projects/app/src/pageComponents/dataset/list/CreateModal.tsx b/projects/app/src/pageComponents/dataset/list/CreateModal.tsx index cd3742f143b1..5e9005191810 100644 --- a/projects/app/src/pageComponents/dataset/list/CreateModal.tsx +++ b/projects/app/src/pageComponents/dataset/list/CreateModal.tsx @@ -2,7 +2,6 @@ import React, { useMemo } from 'react'; import { Box, Flex, Button, ModalFooter, ModalBody, Input, HStack } from '@chakra-ui/react'; import { useSelectFile } from '@/web/common/file/hooks/useSelectFile'; import { useForm } from 'react-hook-form'; -import { useToast } from '@fastgpt/web/hooks/useToast'; import { useRouter } from 'next/router'; import { useSystemStore } from '@/web/common/system/useSystemStore'; import { useRequest2 } from '@fastgpt/web/hooks/useRequest'; @@ -41,7 +40,8 @@ const CreateModal = ({ }) => { const { t } = useTranslation(); const router = useRouter(); - const { defaultModels, embeddingModelList, datasetModelList } = useSystemStore(); + const { feConfigs, defaultModels, embeddingModelList, datasetModelList, getVlmModelList } = + useSystemStore(); const { isPc } = useSystem(); const datasetTypeMap = useMemo(() => { @@ -71,6 +71,8 @@ const CreateModal = ({ const filterNotHiddenVectorModelList = embeddingModelList.filter((item) => !item.hidden); + const vllmModelList = useMemo(() => getVlmModelList(), [getVlmModelList]); + const form = useForm<CreateDatasetParams>({ defaultValues: { parentId, @@ -81,13 +83,15 @@ const CreateModal = ({ vectorModel: defaultModels.embedding?.model || getWebDefaultEmbeddingModel(embeddingModelList)?.model, agentModel: - defaultModels.datasetTextLLM?.model || getWebDefaultLLMModel(datasetModelList)?.model + defaultModels.datasetTextLLM?.model || getWebDefaultLLMModel(datasetModelList)?.model, + vlmModel: defaultModels.datasetImageLLM?.model } }); const { register, setValue, handleSubmit, watch } = form; const avatar = watch('avatar'); const vectorModel = watch('vectorModel'); const agentModel = watch('agentModel'); + const vlmModel = watch('vlmModel'); const { File, @@ -174,6 +178,7 @@ const CreateModal = ({ /> </Flex> </Box> + <Flex mt={6} alignItems={['flex-start', 'center']} @@ -206,6 +211,7 @@ const CreateModal = ({ /> </Box> </Flex> + <Flex mt={6} alignItems={['flex-start', 'center']} @@ -232,11 +238,45 @@ const CreateModal = ({ value: item.model }))} onchange={(e) => { - setValue('agentModel' as const, e); + setValue('agentModel', e); }} /> </Box> </Flex> + + {feConfigs?.isPlus && ( + <Flex + mt={6} + alignItems={['flex-start', 'center']} + justify={'space-between'} + flexDir={['column', 'row']} + > + <HStack + spacing={1} + flex={['', '0 0 110px']} + fontSize={'sm'} + color={'myGray.900'} + fontWeight={500} + pb={['12px', '0']} + > + <Box>{t('dataset:vllm_model')}</Box> + </HStack> + <Box w={['100%', '300px']}> + <AIModelSelector + w={['100%', '300px']} + value={vlmModel} + list={vllmModelList.map((item) => ({ + label: item.name, + value: item.model + }))} + onchange={(e) => { + setValue('vlmModel', e); + }} + /> + </Box> + </Flex> + )} + {/* @ts-ignore */} <ApiDatasetForm type={type} form={form} /> </ModalBody> diff --git a/projects/app/src/pages/account/model/index.tsx b/projects/app/src/pages/account/model/index.tsx index 4c796bb0024d..b833716ad85a 100644 --- a/projects/app/src/pages/account/model/index.tsx +++ b/projects/app/src/pages/account/model/index.tsx @@ -7,13 +7,17 @@ import { useUserStore } from '@/web/support/user/useUserStore'; import FillRowTabs from '@fastgpt/web/components/common/Tabs/FillRowTabs'; import { useTranslation } from 'next-i18next'; import dynamic from 'next/dynamic'; +import { useSystemStore } from '@/web/common/system/useSystemStore'; const ModelConfigTable = dynamic(() => import('@/pageComponents/account/model/ModelConfigTable')); +const ChannelTable = dynamic(() => import('@/pageComponents/account/model/Channel')); +const ChannelLog = dynamic(() => import('@/pageComponents/account/model/Log')); -type TabType = 'model' | 'config' | 'channel'; +type TabType = 'model' | 'config' | 'channel' | 'channel_log'; const ModelProvider = () => { const { t } = useTranslation(); + const { feConfigs } = useSystemStore(); const [tab, setTab] = useState<TabType>('model'); @@ -22,21 +26,29 @@ const ModelProvider = () => { <FillRowTabs<TabType> list={[ { label: t('account:active_model'), value: 'model' }, - { label: t('account:config_model'), value: 'config' } - // { label: t('account:channel'), value: 'channel' } + { label: t('account:config_model'), value: 'config' }, + // @ts-ignore + ...(feConfigs?.show_aiproxy + ? [ + { label: t('account:channel'), value: 'channel' }, + { label: t('account_model:log'), value: 'channel_log' } + ] + : []) ]} value={tab} py={1} onChange={setTab} /> ); - }, [t, tab]); + }, [feConfigs.show_aiproxy, t, tab]); return ( <AccountContainer> <Flex h={'100%'} flexDirection={'column'} gap={4} py={4} px={6}> {tab === 'model' && <ValidModelTable Tab={Tab} />} {tab === 'config' && <ModelConfigTable Tab={Tab} />} + {tab === 'channel' && <ChannelTable Tab={Tab} />} + {tab === 'channel_log' && <ChannelLog Tab={Tab} />} </Flex> </AccountContainer> ); @@ -45,7 +57,7 @@ const ModelProvider = () => { export async function getServerSideProps(content: any) { return { props: { - ...(await serviceSideProps(content, ['account'])) + ...(await serviceSideProps(content, ['account', 'account_model'])) } }; } diff --git a/projects/app/src/pages/api/__mocks__/base.ts b/projects/app/src/pages/api/__mocks__/base.ts deleted file mode 100644 index 74fd415409c0..000000000000 --- a/projects/app/src/pages/api/__mocks__/base.ts +++ /dev/null @@ -1,130 +0,0 @@ -import { MongoMemoryReplSet } from 'mongodb-memory-server'; -import mongoose from 'mongoose'; -import { parseHeaderCertMock } from '@/test/utils'; -import { initMockData, root } from './db/init'; -import { faker } from '@faker-js/faker/locale/zh_CN'; - -jest.mock('nanoid', () => { - return { - nanoid: () => {} - }; -}); - -jest.mock('@fastgpt/global/common/string/tools', () => { - return { - hashStr(str: string) { - return str; - }, - getNanoid() { - return faker.string.alphanumeric(12); - } - }; -}); - -jest.mock('@fastgpt/service/common/system/log', () => ({ - addLog: { - log: jest.fn(), - warn: jest.fn((...prop) => { - console.warn(prop); - }), - error: jest.fn((...prop) => { - console.error(prop); - }), - info: jest.fn(), - debug: jest.fn() - } -})); - -jest.setMock( - '@fastgpt/service/support/permission/controller', - (() => { - const origin = jest.requireActual< - typeof import('@fastgpt/service/support/permission/controller') - >('@fastgpt/service/support/permission/controller'); - - return { - ...origin, - parseHeaderCert: parseHeaderCertMock - }; - })() -); - -jest.mock('@/service/middleware/entry', () => { - return { - NextAPI: (...args: any) => { - return async function api(req: any, res: any) { - try { - let response = null; - for (const handler of args) { - response = await handler(req, res); - } - return { - code: 200, - data: response - }; - } catch (error) { - return { - code: 500, - error - }; - } - }; - } - }; -}); - -beforeAll(async () => { - // 新建一个内存数据库,然后让 mongoose 连接这个数据库 - if (!global.mongod || !global.mongodb) { - const replSet = new MongoMemoryReplSet({ - instanceOpts: [ - { - storageEngine: 'wiredTiger' - }, - { - storageEngine: 'wiredTiger' - } - ] - }); - replSet.start(); - await replSet.waitUntilRunning(); - const uri = replSet.getUri(); - // const mongod = await MongoMemoryServer.create({ - // instance: { - // replSet: 'testset' - // } - // }); - // global.mongod = mongod; - global.replSet = replSet; - global.mongodb = mongoose; - - await global.mongodb.connect(uri, { - dbName: 'fastgpt_test', - bufferCommands: true, - maxConnecting: 50, - maxPoolSize: 50, - minPoolSize: 20, - connectTimeoutMS: 60000, - waitQueueTimeoutMS: 60000, - socketTimeoutMS: 60000, - maxIdleTimeMS: 300000, - retryWrites: true, - retryReads: true - }); - - await initMockData(); - console.log(root); - } -}); - -afterAll(async () => { - if (global.mongodb) { - await global.mongodb.disconnect(); - } - if (global.replSet) { - await global.replSet.stop(); - } - if (global.mongod) { - await global.mongod.stop(); - } -}); diff --git a/projects/app/src/pages/api/__mocks__/db/init.ts b/projects/app/src/pages/api/__mocks__/db/init.ts deleted file mode 100644 index 33514c02e099..000000000000 --- a/projects/app/src/pages/api/__mocks__/db/init.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { DefaultGroupName } from '@fastgpt/global/support/user/team/group/constant'; -import { MongoApp } from '@fastgpt/service/core/app/schema'; -import { MongoMemberGroupModel } from '@fastgpt/service/support/permission/memberGroup/memberGroupSchema'; -import { MongoUser } from '@fastgpt/service/support/user/schema'; -import { MongoTeamMember } from '@fastgpt/service/support/user/team/teamMemberSchema'; -import { MongoTeam } from '@fastgpt/service/support/user/team/teamSchema'; - -export const root = { - uid: '', - tmbId: '', - teamId: '', - isRoot: true, - appId: '' -}; - -export const initMockData = async () => { - const [rootUser] = await MongoUser.create([ - { - username: 'root', - password: '123456' - } - ]); - root.uid = String(rootUser._id); - const [rootTeam] = await MongoTeam.create([ - { - name: 'root Team' - } - ]); - root.teamId = String(rootTeam._id); - const [rootTmb] = await MongoTeamMember.create([ - { - teamId: rootTeam._id, - name: 'owner', - role: 'owner', - userId: rootUser._id, - status: 'active' - } - ]); - root.tmbId = String(rootTmb._id); - await MongoMemberGroupModel.create([ - { - name: DefaultGroupName, - teamId: rootTeam._id - } - ]); - - const [rootApp] = await MongoApp.create([ - { - name: 'root Test App', - teamId: rootTeam._id, - tmbId: rootTmb._id - } - ]); - - root.appId = String(rootApp._id); -}; diff --git a/projects/app/src/pages/api/__mocks__/demo/demo.test.ts b/projects/app/src/pages/api/__mocks__/demo/demo.test.ts deleted file mode 100644 index be5b9ec738ca..000000000000 --- a/projects/app/src/pages/api/__mocks__/demo/demo.test.ts +++ /dev/null @@ -1,61 +0,0 @@ -import '@/pages/api/__mocks__/base'; -import { root } from '@/pages/api/__mocks__/db/init'; -import { getTestRequest } from '@/test/utils'; -import { AppErrEnum } from '@fastgpt/global/common/error/code/app'; -import handler from './demo'; - -// Import the schema -import { MongoOutLink } from '@fastgpt/service/support/outLink/schema'; - -beforeAll(async () => { - // await MongoOutLink.create({ - // shareId: 'aaa', - // appId: root.appId, - // tmbId: root.tmbId, - // teamId: root.teamId, - // type: 'share', - // name: 'aaa' - // }) -}); - -test('Should return a list of outLink', async () => { - // Mock request - const res = (await handler( - ...getTestRequest({ - query: { - appId: root.appId, - type: 'share' - }, - user: root - }) - )) as any; - - expect(res.code).toBe(200); - expect(res.data.length).toBe(2); -}); - -test('appId is required', async () => { - const res = (await handler( - ...getTestRequest({ - query: { - type: 'share' - }, - user: root - }) - )) as any; - expect(res.code).toBe(500); - expect(res.error).toBe(AppErrEnum.unExist); -}); - -test('if type is not provided, return nothing', async () => { - const res = (await handler( - ...getTestRequest({ - query: { - appId: root.appId - }, - user: root - }) - )) as any; - expect(res.code).toBe(200); - expect(res.data.length).toBe(0); -}); diff --git a/projects/app/src/pages/api/__mocks__/demo/demo.ts b/projects/app/src/pages/api/__mocks__/demo/demo.ts deleted file mode 100644 index d0e2ceb3b397..000000000000 --- a/projects/app/src/pages/api/__mocks__/demo/demo.ts +++ /dev/null @@ -1,17 +0,0 @@ -import type { ApiRequestProps, ApiResponseType } from '@fastgpt/service/type/next'; -import { NextAPI } from '@/service/middleware/entry'; - -export type demoQuery = {}; - -export type demoBody = {}; - -export type demoResponse = {}; - -async function handler( - req: ApiRequestProps<demoBody, demoQuery>, - res: ApiResponseType<any> -): Promise<demoResponse> { - return {}; -} - -export default NextAPI(handler); diff --git a/projects/app/src/pages/api/__mocks__/type.d.ts b/projects/app/src/pages/api/__mocks__/type.d.ts deleted file mode 100644 index fe65eb341d03..000000000000 --- a/projects/app/src/pages/api/__mocks__/type.d.ts +++ /dev/null @@ -1,11 +0,0 @@ -import type { MongoMemoryReplSet, MongoMemoryServer } from 'mongodb-memory-server'; -declare global { - var mongod: MongoMemoryServer | undefined; - var replSet: MongoMemoryReplSet | undefined; -} - -export type RequestResponse<T = any> = { - code: number; - error?: string; - data?: T; -}; diff --git a/projects/app/src/pages/api/admin/clearInvalidData.ts b/projects/app/src/pages/api/admin/clearInvalidData.ts index d3f85a6a75c9..6259a01d9640 100644 --- a/projects/app/src/pages/api/admin/clearInvalidData.ts +++ b/projects/app/src/pages/api/admin/clearInvalidData.ts @@ -35,7 +35,7 @@ async function checkInvalidImg(start: Date, end: Date, limit = 50) { 'metadata.relatedImgId': image.metadata?.relatedId }, '_id' - ); + ).lean(); if (!collection) { await image.deleteOne(); diff --git a/projects/app/src/pages/api/admin/initv481.ts b/projects/app/src/pages/api/admin/initv481.ts index ca370235b2b5..b9aa4fec3a6c 100644 --- a/projects/app/src/pages/api/admin/initv481.ts +++ b/projects/app/src/pages/api/admin/initv481.ts @@ -10,6 +10,11 @@ async function handler(req: NextApiRequest, res: NextApiResponse) { // 重命名 dataset.trainigns -> dataset_trainings try { + if (!connectionMongo.connection.db) { + return jsonRes(res, { + message: '数据库连接失败' + }); + } const collections = await connectionMongo.connection.db .listCollections({ name: 'dataset.trainings' }) .toArray(); @@ -31,6 +36,11 @@ async function handler(req: NextApiRequest, res: NextApiResponse) { } try { + if (!connectionMongo.connection.db) { + return jsonRes(res, { + message: '数据库连接失败' + }); + } const collections = await connectionMongo.connection.db .listCollections({ name: 'dataset.collections' }) .toArray(); @@ -52,6 +62,11 @@ async function handler(req: NextApiRequest, res: NextApiResponse) { } try { + if (!connectionMongo.connection.db) { + return jsonRes(res, { + message: '数据库连接失败' + }); + } const collections = await connectionMongo.connection.db .listCollections({ name: 'dataset.datas' }) .toArray(); @@ -73,6 +88,11 @@ async function handler(req: NextApiRequest, res: NextApiResponse) { } try { + if (!connectionMongo.connection.db) { + return jsonRes(res, { + message: '数据库连接失败' + }); + } const collections = await connectionMongo.connection.db .listCollections({ name: 'app.versions' }) .toArray(); @@ -94,6 +114,11 @@ async function handler(req: NextApiRequest, res: NextApiResponse) { } try { + if (!connectionMongo.connection.db) { + return jsonRes(res, { + message: '数据库连接失败' + }); + } const collections = await connectionMongo.connection.db .listCollections({ name: 'buffer.rawtexts' }) .toArray(); @@ -115,6 +140,11 @@ async function handler(req: NextApiRequest, res: NextApiResponse) { } try { + if (!connectionMongo.connection.db) { + return jsonRes(res, { + message: '数据库连接失败' + }); + } const collections = await connectionMongo.connection.db .listCollections({ name: 'buffer.tts' }) .toArray(); @@ -134,6 +164,11 @@ async function handler(req: NextApiRequest, res: NextApiResponse) { } try { + if (!connectionMongo.connection.db) { + return jsonRes(res, { + message: '数据库连接失败' + }); + } const collections = await connectionMongo.connection.db .listCollections({ name: 'team.members' }) .toArray(); @@ -155,6 +190,11 @@ async function handler(req: NextApiRequest, res: NextApiResponse) { } try { + if (!connectionMongo.connection.db) { + return jsonRes(res, { + message: '数据库连接失败' + }); + } const collections = await connectionMongo.connection.db .listCollections({ name: 'team.tags' }) .toArray(); @@ -174,6 +214,11 @@ async function handler(req: NextApiRequest, res: NextApiResponse) { } try { + if (!connectionMongo.connection.db) { + return jsonRes(res, { + message: '数据库连接失败' + }); + } const collections = await connectionMongo.connection.db .listCollections({ name: 'team.subscriptions' }) .toArray(); diff --git a/projects/app/src/pages/api/admin/initv4823.ts b/projects/app/src/pages/api/admin/initv4823.ts new file mode 100644 index 000000000000..45ab2c449e7f --- /dev/null +++ b/projects/app/src/pages/api/admin/initv4823.ts @@ -0,0 +1,208 @@ +import type { NextApiRequest, NextApiResponse } from 'next'; +import { jsonRes } from '@fastgpt/service/common/response'; +import { connectToDatabase } from '@/service/mongo'; +import { authCert } from '@fastgpt/service/support/permission/auth/common'; +import { addHours } from 'date-fns'; +import { MongoDatasetCollection } from '@fastgpt/service/core/dataset/collection/schema'; +import { MongoDataset } from '@fastgpt/service/core/dataset/schema'; +import { delay, retryFn } from '@fastgpt/global/common/system/utils'; +import { delCollection } from '@fastgpt/service/core/dataset/collection/controller'; +import { mongoSessionRun } from '@fastgpt/service/common/mongo/sessionRun'; +import { MongoDatasetDataText } from '@fastgpt/service/core/dataset/data/dataTextSchema'; +import { MongoDatasetData } from '@fastgpt/service/core/dataset/data/schema'; +import { DatasetCollectionSchemaType } from '@fastgpt/global/core/dataset/type'; +import { MongoDatasetTraining } from '@fastgpt/service/core/dataset/training/schema'; +import { deleteDatasetDataVector } from '@fastgpt/service/common/vectorStore/controller'; + +// 删了库,没删集合 +const checkInvalidCollection = async () => { + const batchSize = 1000; + + let skip = 0; + let success = 0; + while (true) { + try { + const collections = await MongoDatasetCollection.find( + {}, + '_id teamId datasetId fileId metadata' + ) + .limit(batchSize) + .skip(skip) + .lean(); + if (collections.length === 0) break; + + const datasetMap: Record<string, DatasetCollectionSchemaType[]> = {}; + + // 相同 datasetId 的集合放到一起 + for await (const collection of collections) { + const datasetId = String(collection.datasetId); + const val = datasetMap[datasetId]; + if (val) { + val.push(collection); + } else { + datasetMap[datasetId] = [collection]; + } + } + + const datasetIds = Object.keys(datasetMap); + for await (const datasetId of datasetIds) { + try { + const val = datasetMap[datasetId]; + if (!val) { + continue; + } + + await retryFn(async () => { + const datasetExists = await MongoDataset.findById(datasetId, '_id').lean(); + if (!datasetExists) { + console.log('清理无效的知识库集合, datasetId', datasetId); + await mongoSessionRun(async (session) => { + return await delCollection({ + collections: val, + delImg: true, + delFile: true, + session + }); + }); + } + }); + } catch (error) { + console.log(error); + } + } + + success += batchSize; + skip += batchSize; + console.log(`检测集合完成:${success}`); + } catch (error) { + console.log(error); + await delay(1000); + } + } +}; + +// 删了集合,没删 data +const checkInvalidData = async () => { + try { + const datas = (await MongoDatasetData.aggregate([ + { + $group: { + _id: '$collectionId', + teamId: { $first: '$teamId' }, + datasetId: { $first: '$datasetId' }, + collectionId: { $first: '$collectionId' } + } + } + ])) as { + _id: string; + teamId: string; + datasetId: string; + collectionId: string; + }[]; + console.log('Total data collections length', datas.length); + // 批量获取集合 + const collections = await MongoDatasetCollection.find({}, '_id').lean(); + console.log('Total collection length', collections.length); + const collectionMap: Record<string, DatasetCollectionSchemaType> = {}; + for await (const collection of collections) { + collectionMap[collection._id] = collection; + } + // 逐一删除无效的集合内容 + for await (const data of datas) { + try { + const col = collectionMap[data.collectionId]; + if (!col) { + console.log('清理无效的知识库集合内容, collectionId', data.collectionId); + await retryFn(async () => { + await MongoDatasetTraining.deleteMany({ + teamId: data.teamId, + datasetId: data.datasetId, + collectionId: data.collectionId + }); + await MongoDatasetDataText.deleteMany({ + teamId: data.teamId, + datasetId: data.datasetId, + collectionId: data.collectionId + }); + await deleteDatasetDataVector({ + teamId: data.teamId, + datasetIds: [data.datasetId], + collectionIds: [data.collectionId] + }); + await MongoDatasetData.deleteMany({ + teamId: data.teamId, + datasetId: data.datasetId, + collectionId: data.collectionId + }); + }); + } + } catch (error) { + console.log(error); + } + } + + console.log(`检测集合完成`); + } catch (error) { + console.log('checkInvalidData error', error); + } +}; + +// 删了data,没删 data_text +const checkInvalidDataText = async () => { + try { + // 获取所有索引层的 dataId + const dataTexts = await MongoDatasetDataText.find({}, 'dataId').lean(); + const dataIds = dataTexts.map((item) => String(item.dataId)); + console.log('Total data_text dataIds:', dataIds.length); + + // 获取数据层的 dataId + const datas = await MongoDatasetData.find({}, '_id').lean(); + const datasSet = new Set(datas.map((item) => String(item._id))); + console.log('Total data length:', datas.length); + + // 存在索引层,不存在数据层的 dataId,说明数据已经被删了 + const unExistsSet = dataIds.filter((id) => !datasSet.has(id)); + console.log('Total unExists dataIds:', unExistsSet.length); + await MongoDatasetDataText.deleteMany({ + dataId: { $in: unExistsSet } + }); + } catch (error) { + console.log('checkInvalidDataText error', error); + } +}; + +/* pg 中的数据搬到 mongo dataset.datas 中,并做映射 */ +export default async function handler(req: NextApiRequest, res: NextApiResponse) { + try { + await connectToDatabase(); + await authCert({ req, authRoot: true }); + const { start = -2, end = -360 * 24 } = req.body as { start: number; end: number }; + + (async () => { + try { + // 360天 ~ 2小时前 + const endTime = addHours(new Date(), start); + const startTime = addHours(new Date(), end); + console.log('清理无效的集合'); + await checkInvalidCollection(); + console.log('清理无效的数据'); + await checkInvalidData(); + console.log('清理无效的data_text'); + await checkInvalidDataText(); + } catch (error) { + console.log('执行脏数据清理任务出错了'); + } + })(); + + jsonRes(res, { + message: 'success' + }); + } catch (error) { + console.log(error); + + jsonRes(res, { + code: 500, + error + }); + } +} diff --git a/projects/app/src/pages/api/admin/initv485.ts b/projects/app/src/pages/api/admin/initv485.ts index 5aac3723e610..53c2b7d882f4 100644 --- a/projects/app/src/pages/api/admin/initv485.ts +++ b/projects/app/src/pages/api/admin/initv485.ts @@ -63,7 +63,7 @@ async function initHttp(teamId?: string): Promise<any> { } } ], - { session } + { session, ordered: true } ); /* 批量创建子插件 */ @@ -88,7 +88,7 @@ async function initHttp(teamId?: string): Promise<any> { } } ], - { session } + { session, ordered: true } ); if (item.version === 'v2') { await MongoAppVersion.create( @@ -100,7 +100,7 @@ async function initHttp(teamId?: string): Promise<any> { edges: item.edges } ], - { session } + { session, ordered: true } ); } } @@ -160,7 +160,7 @@ async function initPlugin(teamId?: string): Promise<any> { } } ], - { session } + { session, ordered: true } ); if (plugin.version === 'v2') { @@ -173,7 +173,7 @@ async function initPlugin(teamId?: string): Promise<any> { edges: plugin.edges } ], - { session } + { session, ordered: true } ); } diff --git a/projects/app/src/pages/api/admin/initv490.ts b/projects/app/src/pages/api/admin/initv490.ts new file mode 100644 index 000000000000..f18e9bc8e66f --- /dev/null +++ b/projects/app/src/pages/api/admin/initv490.ts @@ -0,0 +1,76 @@ +import { NextAPI } from '@/service/middleware/entry'; +import { authCert } from '@fastgpt/service/support/permission/auth/common'; +import { NextApiRequest, NextApiResponse } from 'next'; +import { MongoDatasetCollection } from '@fastgpt/service/core/dataset/collection/schema'; +import { DatasetCollectionDataProcessModeEnum } from '@fastgpt/global/core/dataset/constants'; +import { MongoDatasetData } from '@fastgpt/service/core/dataset/data/schema'; +import { DatasetDataIndexTypeEnum } from '@fastgpt/global/core/dataset/data/constants'; +import { PgClient } from '@fastgpt/service/common/vectorStore/pg'; +import { PG_ADDRESS } from '@fastgpt/service/common/vectorStore/constants'; + +// 所有 trainingType=auto 的 collection,都改成 trainingType=chunk +const updateCollections = async () => { + await MongoDatasetCollection.updateMany( + { + trainingType: DatasetCollectionDataProcessModeEnum.auto + }, + { + $set: { + trainingType: DatasetCollectionDataProcessModeEnum.chunk, + autoIndexes: true + } + } + ); +}; +const updateData = async () => { + await MongoDatasetData.updateMany({}, [ + { + $set: { + indexes: { + $map: { + input: '$indexes', + as: 'index', + in: { + $mergeObjects: [ + '$$index', + { + type: { + $cond: { + if: { $eq: ['$$index.defaultIndex', true] }, + then: DatasetDataIndexTypeEnum.default, + else: DatasetDataIndexTypeEnum.custom + } + } + } + ] + } + } + } + } + } + ]); +}; +const upgradePgVector = async () => { + if (!PG_ADDRESS) return; + await PgClient.query(` + ALTER EXTENSION vector UPDATE; + `); +}; + +async function handler(req: NextApiRequest, _res: NextApiResponse) { + await authCert({ req, authRoot: true }); + + console.log('升级 PG vector 插件'); + await upgradePgVector(); + + console.log('变更所有 collection 的 trainingType 为 chunk'); + await updateCollections(); + + console.log( + "更新所有 data 的 index, autoIndex=true 的,增加type='default',其他的增加 type='custom'" + ); + await updateData(); + return { success: true }; +} + +export default NextAPI(handler); diff --git a/projects/app/src/pages/api/admin/resetMilvus.ts b/projects/app/src/pages/api/admin/resetMilvus.ts index 2d5e47b0114b..c465ff3f5c5d 100644 --- a/projects/app/src/pages/api/admin/resetMilvus.ts +++ b/projects/app/src/pages/api/admin/resetMilvus.ts @@ -98,7 +98,8 @@ async function handler( } ], { - session + session, + ordered: true } ); } diff --git a/projects/app/src/pages/api/aiproxy/[...path].ts b/projects/app/src/pages/api/aiproxy/[...path].ts new file mode 100644 index 000000000000..f63d7500c507 --- /dev/null +++ b/projects/app/src/pages/api/aiproxy/[...path].ts @@ -0,0 +1,76 @@ +import type { NextApiRequest, NextApiResponse } from 'next'; +import { jsonRes } from '@fastgpt/service/common/response'; +import { request as httpsRequest } from 'https'; +import { request as httpRequest } from 'http'; +import { authSystemAdmin } from '@fastgpt/service/support/permission/user/auth'; + +const baseUrl = process.env.AIPROXY_API_ENDPOINT; +const token = process.env.AIPROXY_API_TOKEN; + +export default async function handler(req: NextApiRequest, res: NextApiResponse) { + try { + await authSystemAdmin({ req }); + + if (!baseUrl || !token) { + throw new Error('AIPROXY_API_ENDPOINT or AIPROXY_API_TOKEN is not set'); + } + + const { path = [], ...query } = req.query as any; + + if (!path.length) { + throw new Error('url is empty'); + } + + const queryStr = new URLSearchParams(query).toString(); + const requestPath = queryStr + ? `/${path?.join('/')}?${new URLSearchParams(query).toString()}` + : `/${path?.join('/')}`; + + const parsedUrl = new URL(baseUrl); + delete req.headers?.cookie; + delete req.headers?.host; + delete req.headers?.origin; + + // Select request function based on protocol + const requestFn = parsedUrl.protocol === 'https:' ? httpsRequest : httpRequest; + + const requestResult = requestFn({ + protocol: parsedUrl.protocol, + hostname: parsedUrl.hostname, + port: parsedUrl.port, + path: requestPath, + method: req.method, + headers: { + ...req.headers, + Authorization: `Bearer ${token}` + }, + timeout: 30000 + }); + + req.pipe(requestResult); + + requestResult.on('response', (response) => { + Object.keys(response.headers).forEach((key) => { + // @ts-ignore + res.setHeader(key, response.headers[key]); + }); + response.statusCode && res.writeHead(response.statusCode); + response.pipe(res); + }); + requestResult.on('error', (e) => { + res.send(e); + res.end(); + }); + } catch (error) { + jsonRes(res, { + code: 500, + error + }); + } +} + +export const config = { + api: { + bodyParser: false + } +}; diff --git a/projects/app/src/pages/api/aiproxy/api/createChannel.ts b/projects/app/src/pages/api/aiproxy/api/createChannel.ts new file mode 100644 index 000000000000..498454daac1c --- /dev/null +++ b/projects/app/src/pages/api/aiproxy/api/createChannel.ts @@ -0,0 +1,33 @@ +import type { ApiRequestProps, ApiResponseType } from '@fastgpt/service/type/next'; +import { authSystemAdmin } from '@fastgpt/service/support/permission/user/auth'; +import axios from 'axios'; +import { getErrText } from '@fastgpt/global/common/error/utils'; + +const baseUrl = process.env.AIPROXY_API_ENDPOINT; +const token = process.env.AIPROXY_API_TOKEN; + +async function handler(req: ApiRequestProps, res: ApiResponseType<any>) { + try { + await authSystemAdmin({ req }); + + if (!baseUrl || !token) { + return Promise.reject('AIPROXY_API_ENDPOINT or AIPROXY_API_TOKEN is not set'); + } + + const { data } = await axios.post(`${baseUrl}/api/channel/`, req.body, { + headers: { + Authorization: `Bearer ${token}` + } + }); + + res.json(data); + } catch (error) { + res.json({ + success: false, + message: getErrText(error), + data: error + }); + } +} + +export default handler; diff --git a/projects/app/src/pages/api/common/file/previewContent.ts b/projects/app/src/pages/api/common/file/previewContent.ts deleted file mode 100644 index cfe4d16814dc..000000000000 --- a/projects/app/src/pages/api/common/file/previewContent.ts +++ /dev/null @@ -1,78 +0,0 @@ -/* - Read db file content and response 3000 words -*/ -import type { NextApiResponse } from 'next'; -import { authCollectionFile } from '@fastgpt/service/support/permission/auth/file'; -import { NextAPI } from '@/service/middleware/entry'; -import { DatasetSourceReadTypeEnum } from '@fastgpt/global/core/dataset/constants'; -import { readDatasetSourceRawText } from '@fastgpt/service/core/dataset/read'; -import { ApiRequestProps } from '@fastgpt/service/type/next'; -import { - OwnerPermissionVal, - WritePermissionVal -} from '@fastgpt/global/support/permission/constant'; -import { authDataset } from '@fastgpt/service/support/permission/dataset/auth'; - -export type PreviewContextProps = { - datasetId: string; - type: DatasetSourceReadTypeEnum; - sourceId: string; - isQAImport?: boolean; - selector?: string; - externalFileId?: string; -}; - -async function handler(req: ApiRequestProps<PreviewContextProps>, res: NextApiResponse<any>) { - const { type, sourceId, isQAImport, selector, datasetId, externalFileId } = req.body; - - if (!sourceId) { - throw new Error('fileId is empty'); - } - - const { teamId, apiServer, feishuServer, yuqueServer } = await (async () => { - if (type === DatasetSourceReadTypeEnum.fileLocal) { - const res = await authCollectionFile({ - req, - authToken: true, - authApiKey: true, - fileId: sourceId, - per: OwnerPermissionVal - }); - return { - teamId: res.teamId - }; - } - const { dataset } = await authDataset({ - req, - authApiKey: true, - authToken: true, - datasetId, - per: WritePermissionVal - }); - return { - teamId: dataset.teamId, - apiServer: dataset.apiServer, - feishuServer: dataset.feishuServer, - yuqueServer: dataset.yuqueServer - }; - })(); - - const rawText = await readDatasetSourceRawText({ - teamId, - type, - sourceId, - isQAImport, - selector, - apiServer, - feishuServer, - yuqueServer, - externalFileId - }); - - return { - previewContent: rawText.slice(0, 3000), - totalLength: rawText.length - }; -} - -export default NextAPI(handler); diff --git a/projects/app/src/pages/api/core/ai/model/test.ts b/projects/app/src/pages/api/core/ai/model/test.ts index 54ddb97588c2..aa9c1e65848a 100644 --- a/projects/app/src/pages/api/core/ai/model/test.ts +++ b/projects/app/src/pages/api/core/ai/model/test.ts @@ -60,6 +60,7 @@ const testLLMModel = async (model: LLMModelItemType) => { const ai = getAIApi({ timeout: 10000 }); + const requestBody = llmCompletionsBodyFormat( { model: model.model, @@ -79,8 +80,10 @@ const testLLMModel = async (model: LLMModelItemType) => { }); const responseText = response.choices?.[0]?.message?.content; + // @ts-ignore + const reasoning_content = response.choices?.[0]?.message?.reasoning_content; - if (!responseText) { + if (!responseText && !reasoning_content) { return Promise.reject('Model response empty'); } diff --git a/projects/app/src/pages/api/core/app/create.ts b/projects/app/src/pages/api/core/app/create.ts index d9f725f50499..1eb64b8402ee 100644 --- a/projects/app/src/pages/api/core/app/create.ts +++ b/projects/app/src/pages/api/core/app/create.ts @@ -126,7 +126,7 @@ export const onCreateApp = async ({ 'pluginData.nodeVersion': defaultNodeVersion } ], - { session } + { session, ordered: true } ); if (!AppFolderTypeList.includes(type!)) { @@ -144,7 +144,7 @@ export const onCreateApp = async ({ isPublish: true } ], - { session } + { session, ordered: true } ); } diff --git a/projects/app/src/pages/api/core/app/exportChatLogs.ts b/projects/app/src/pages/api/core/app/exportChatLogs.ts index 94fe0edeefbb..225dfffea3d3 100644 --- a/projects/app/src/pages/api/core/app/exportChatLogs.ts +++ b/projects/app/src/pages/api/core/app/exportChatLogs.ts @@ -88,6 +88,7 @@ async function handler(req: ApiRequestProps<ExportChatLogsBody, {}>, res: NextAp } } }, + { $sort: { _id: 1 } }, { $project: { value: 1, diff --git a/projects/app/src/pages/api/core/app/folder/create.ts b/projects/app/src/pages/api/core/app/folder/create.ts index eb05ee1b4e20..c27b5269d602 100644 --- a/projects/app/src/pages/api/core/app/folder/create.ts +++ b/projects/app/src/pages/api/core/app/folder/create.ts @@ -89,7 +89,8 @@ async function handler(req: ApiRequestProps<CreateAppFolderBody>) { } ], { - session + session, + ordered: true } ); } diff --git a/projects/app/src/pages/api/core/app/httpPlugin/getApiSchemaByUrl.ts b/projects/app/src/pages/api/core/app/httpPlugin/getApiSchemaByUrl.ts index 3ad92c46effe..48780eb6ecf2 100644 --- a/projects/app/src/pages/api/core/app/httpPlugin/getApiSchemaByUrl.ts +++ b/projects/app/src/pages/api/core/app/httpPlugin/getApiSchemaByUrl.ts @@ -1,18 +1,23 @@ import type { NextApiRequest, NextApiResponse } from 'next'; -import { jsonRes } from '@fastgpt/service/common/response'; import { loadOpenAPISchemaFromUrl } from '@fastgpt/global/common/string/swagger'; +import { NextAPI } from '@/service/middleware/entry'; +import { CommonErrEnum } from '@fastgpt/global/common/error/code/common'; +import { isInternalAddress } from '@fastgpt/service/common/system/utils'; -export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) { - try { - const apiURL = req.body.url as string; +async function handler(req: NextApiRequest, res: NextApiResponse<any>) { + const apiURL = req.body.url as string; - return jsonRes(res, { - data: await loadOpenAPISchemaFromUrl(apiURL) - }); - } catch (err) { - jsonRes(res, { - code: 500, - error: err - }); + if (!apiURL) { + return Promise.reject(CommonErrEnum.missingParams); } + + const isInternal = isInternalAddress(apiURL); + + if (isInternal) { + return Promise.reject('Invalid url'); + } + + return await loadOpenAPISchemaFromUrl(apiURL); } + +export default NextAPI(handler); diff --git a/projects/app/src/pages/api/core/app/version/latest.test.ts b/projects/app/src/pages/api/core/app/version/latest.test.ts deleted file mode 100644 index 7e4f8f83cf45..000000000000 --- a/projects/app/src/pages/api/core/app/version/latest.test.ts +++ /dev/null @@ -1,55 +0,0 @@ -import '@/pages/api/__mocks__/base'; -import { root } from '@/pages/api/__mocks__/db/init'; -import { getTestRequest } from '@/test/utils'; -import handler, { getLatestVersionQuery, getLatestVersionResponse } from './latest'; -import { MongoAppVersion } from '@fastgpt/service/core/app/version/schema'; - -beforeAll(async () => { - // 创建3个测试数据,其中2个是已发布的 - await MongoAppVersion.create([ - { - appId: root.appId, - nodes: [1], - edges: [], - chatConfig: {}, - isPublish: false, - versionName: 'v1', - tmbId: root.tmbId, - time: new Date('2023-01-01') - }, - { - appId: root.appId, - nodes: [2], - edges: [], - chatConfig: {}, - isPublish: true, - versionName: 'v2', - tmbId: root.tmbId, - time: new Date('2023-01-02') - }, - { - appId: root.appId, - nodes: [3], - edges: [], - chatConfig: {}, - isPublish: false, - versionName: 'v3', - tmbId: root.tmbId, - time: new Date('2023-01-03') - } - ]); -}); - -test('获取最新版本并检查', async () => { - const _res = (await handler( - ...getTestRequest<{}, getLatestVersionQuery>({ - query: { - appId: root.appId - }, - user: root - }) - )) as any; - const res = _res.data as getLatestVersionResponse; - - expect(res.nodes[0]).toEqual(2); -}); diff --git a/projects/app/src/pages/api/core/app/version/lis.test.ts b/projects/app/src/pages/api/core/app/version/lis.test.ts deleted file mode 100644 index 078d2b5e4b11..000000000000 --- a/projects/app/src/pages/api/core/app/version/lis.test.ts +++ /dev/null @@ -1,69 +0,0 @@ -import '@/pages/api/__mocks__/base'; -import { root } from '@/pages/api/__mocks__/db/init'; -import { getTestRequest } from '@/test/utils'; -import handler, { versionListBody, versionListResponse } from './list'; - -// Import the schema -import { MongoAppVersion } from '@fastgpt/service/core/app/version/schema'; - -const total = 22; - -beforeAll(async () => { - const arr = new Array(total).fill(0); - await MongoAppVersion.insertMany( - arr.map((_, index) => ({ - appId: root.appId, - nodes: [], - edges: [], - chatConfig: {}, - isPublish: index % 2 === 0, - versionName: `v` + index, - tmbId: root.tmbId, - time: new Date(index * 1000) - })) - ); -}); - -test('Get version list and check', async () => { - const offset = 0; - const pageSize = 10; - - const _res = (await handler( - ...getTestRequest<{}, versionListBody>({ - body: { - offset, - pageSize, - appId: root.appId - }, - user: root - }) - )) as any; - const res = _res.data as versionListResponse; - - expect(res.total).toBe(total); - expect(res.list.length).toBe(pageSize); - expect(res.list[0].versionName).toBe('v21'); - expect(res.list[9].versionName).toBe('v12'); -}); - -test('Get version list with offset 20', async () => { - const offset = 20; - const pageSize = 10; - - const _res = (await handler( - ...getTestRequest<{}, versionListBody>({ - body: { - offset, - pageSize, - appId: root.appId - }, - user: root - }) - )) as any; - const res = _res.data as versionListResponse; - - expect(res.total).toBe(total); - expect(res.list.length).toBe(2); - expect(res.list[0].versionName).toBe('v1'); - expect(res.list[1].versionName).toBe('v0'); -}); diff --git a/projects/app/src/pages/api/core/app/version/list.test.ts b/projects/app/src/pages/api/core/app/version/list.test.ts new file mode 100644 index 000000000000..759886779b4a --- /dev/null +++ b/projects/app/src/pages/api/core/app/version/list.test.ts @@ -0,0 +1,35 @@ +import { MongoApp } from '@fastgpt/service/core/app/schema'; +import { MongoAppVersion } from '@fastgpt/service/core/app/version/schema'; +import { getRootUser } from '@test/datas/users'; +import { Call } from '@test/utils/request'; +import { describe, expect, it } from 'vitest'; +import handler, { type versionListBody, type versionListResponse } from './list'; + +describe('app version list test', () => { + it('should return app version list', async () => { + const root = await getRootUser(); + const app = await MongoApp.create({ + name: 'test', + tmbId: root.tmbId, + teamId: root.teamId + }); + await MongoAppVersion.create( + [...Array(10).keys()].map((i) => ({ + tmbId: root.tmbId, + appId: app._id, + versionName: `v${i}` + })) + ); + const res = await Call<versionListBody, {}, versionListResponse>(handler, { + auth: root, + body: { + pageSize: 10, + offset: 0, + appId: app._id + } + }); + expect(res.code).toBe(200); + expect(res.data.total).toBe(10); + expect(res.data.list.length).toBe(10); + }); +}); diff --git a/projects/app/src/pages/api/core/app/version/publish.test.ts b/projects/app/src/pages/api/core/app/version/publish.test.ts deleted file mode 100644 index 314af025225c..000000000000 --- a/projects/app/src/pages/api/core/app/version/publish.test.ts +++ /dev/null @@ -1,36 +0,0 @@ -import '@/pages/api/__mocks__/base'; -import { root } from '@/pages/api/__mocks__/db/init'; -import { getTestRequest } from '@/test/utils'; -import handler from './publish'; -import { MongoAppVersion } from '@fastgpt/service/core/app/version/schema'; -import { PostPublishAppProps } from '@/global/core/app/api'; -import { AppTypeEnum } from '@fastgpt/global/core/app/constants'; - -describe('发布应用版本测试', () => { - test('发布一个未发布的版本', async () => { - const publishData: PostPublishAppProps = { - nodes: [], - edges: [], - chatConfig: {}, - isPublish: false, - versionName: '1' - }; - - await handler( - ...getTestRequest<{ appId: string }, PostPublishAppProps>({ - body: publishData, - query: { appId: root.appId }, - user: root - }) - ); - - // 检查数据库是否插入成功 - const insertedVersion = await MongoAppVersion.countDocuments(); - - console.log(insertedVersion, '==-'); - - // expect(insertedVersion).toBeTruthy(); - // expect(insertedVersion?.isPublish).toBe(false); - // expect(insertedVersion?.versionName).toBe('1'); - }); -}); diff --git a/projects/app/src/pages/api/core/app/version/publish.ts b/projects/app/src/pages/api/core/app/version/publish.ts index ad052820b7a7..048a41b95f02 100644 --- a/projects/app/src/pages/api/core/app/version/publish.ts +++ b/projects/app/src/pages/api/core/app/version/publish.ts @@ -45,7 +45,7 @@ async function handler(req: ApiRequestProps<PostPublishAppProps>, res: NextApiRe tmbId } ], - { session } + { session, ordered: true } ); // update app diff --git a/projects/app/src/pages/api/core/chat/chatTest.ts b/projects/app/src/pages/api/core/chat/chatTest.ts index d7276191bae8..dbbe6faadf43 100644 --- a/projects/app/src/pages/api/core/chat/chatTest.ts +++ b/projects/app/src/pages/api/core/chat/chatTest.ts @@ -204,44 +204,42 @@ async function handler(req: NextApiRequest, res: NextApiResponse) { }); // save chat - if (!res.closed) { - const isInteractiveRequest = !!getLastInteractiveValue(histories); - const { text: userInteractiveVal } = chatValue2RuntimePrompt(userQuestion.value); + const isInteractiveRequest = !!getLastInteractiveValue(histories); + const { text: userInteractiveVal } = chatValue2RuntimePrompt(userQuestion.value); - const newTitle = isPlugin - ? variables.cTime ?? getSystemTime(timezone) - : getChatTitleFromChatMessage(userQuestion); + const newTitle = isPlugin + ? variables.cTime ?? getSystemTime(timezone) + : getChatTitleFromChatMessage(userQuestion); - const aiResponse: AIChatItemType & { dataId?: string } = { - dataId: responseChatItemId, - obj: ChatRoleEnum.AI, - value: assistantResponses, - [DispatchNodeResponseKeyEnum.nodeResponse]: flowResponses - }; + const aiResponse: AIChatItemType & { dataId?: string } = { + dataId: responseChatItemId, + obj: ChatRoleEnum.AI, + value: assistantResponses, + [DispatchNodeResponseKeyEnum.nodeResponse]: flowResponses + }; - if (isInteractiveRequest) { - await updateInteractiveChat({ - chatId, - appId: app._id, - userInteractiveVal, - aiResponse, - newVariables - }); - } else { - await saveChat({ - chatId, - appId: app._id, - teamId, - tmbId: tmbId, - nodes, - appChatConfig: chatConfig, - variables: newVariables, - isUpdateUseTime: false, // owner update use time - newTitle, - source: ChatSourceEnum.test, - content: [userQuestion, aiResponse] - }); - } + if (isInteractiveRequest) { + await updateInteractiveChat({ + chatId, + appId: app._id, + userInteractiveVal, + aiResponse, + newVariables + }); + } else { + await saveChat({ + chatId, + appId: app._id, + teamId, + tmbId: tmbId, + nodes, + appChatConfig: chatConfig, + variables: newVariables, + isUpdateUseTime: false, // owner update use time + newTitle, + source: ChatSourceEnum.test, + content: [userQuestion, aiResponse] + }); } createChatUsage({ diff --git a/projects/app/src/pages/api/core/dataset/collection/create/apiCollection.ts b/projects/app/src/pages/api/core/dataset/collection/create/apiCollection.ts index 1950e42f44ea..c4c4d9578857 100644 --- a/projects/app/src/pages/api/core/dataset/collection/create/apiCollection.ts +++ b/projects/app/src/pages/api/core/dataset/collection/create/apiCollection.ts @@ -4,7 +4,8 @@ import { authDataset } from '@fastgpt/service/support/permission/dataset/auth'; import { createCollectionAndInsertData } from '@fastgpt/service/core/dataset/collection/controller'; import { TrainingModeEnum, - DatasetCollectionTypeEnum + DatasetCollectionTypeEnum, + DatasetCollectionDataProcessModeEnum } from '@fastgpt/global/core/dataset/constants'; import { NextAPI } from '@/service/middleware/entry'; @@ -15,15 +16,7 @@ import { MongoDatasetCollection } from '@fastgpt/service/core/dataset/collection import { DatasetErrEnum } from '@fastgpt/global/common/error/code/dataset'; async function handler(req: NextApiRequest): CreateCollectionResponse { - const { - name, - apiFileId, - trainingType = TrainingModeEnum.chunk, - chunkSize = 512, - chunkSplitter, - qaPrompt, - ...body - } = req.body as ApiDatasetCreateDatasetCollectionParams; + const { name, apiFileId, ...body } = req.body as ApiDatasetCreateDatasetCollectionParams; const { teamId, tmbId, dataset } = await authDataset({ req, @@ -56,7 +49,8 @@ async function handler(req: NextApiRequest): CreateCollectionResponse { feishuServer, yuqueServer, apiFileId, - teamId + teamId, + tmbId }); const { collectionId, insertResults } = await createCollectionAndInsertData({ @@ -69,10 +63,6 @@ async function handler(req: NextApiRequest): CreateCollectionResponse { tmbId, type: DatasetCollectionTypeEnum.apiFile, name: name, - trainingType, - chunkSize, - chunkSplitter, - qaPrompt, apiFileId, metadata: { relatedImgId: apiFileId diff --git a/projects/app/src/pages/api/core/dataset/collection/create/csvTable.ts b/projects/app/src/pages/api/core/dataset/collection/create/csvTable.ts index 4438581239f6..f7178492d8c9 100644 --- a/projects/app/src/pages/api/core/dataset/collection/create/csvTable.ts +++ b/projects/app/src/pages/api/core/dataset/collection/create/csvTable.ts @@ -4,6 +4,7 @@ import { authDataset } from '@fastgpt/service/support/permission/dataset/auth'; import { FileIdCreateDatasetCollectionParams } from '@fastgpt/global/core/dataset/api'; import { createCollectionAndInsertData } from '@fastgpt/service/core/dataset/collection/controller'; import { + DatasetCollectionDataProcessModeEnum, DatasetCollectionTypeEnum, TrainingModeEnum } from '@fastgpt/global/core/dataset/constants'; @@ -15,7 +16,6 @@ import { MongoRawTextBuffer } from '@fastgpt/service/common/buffer/rawText/schem async function handler(req: NextApiRequest): CreateCollectionResponse { const { datasetId, parentId, fileId, ...body } = req.body as FileIdCreateDatasetCollectionParams; - const trainingType = TrainingModeEnum.chunk; const { teamId, tmbId, dataset } = await authDataset({ req, authToken: true, @@ -27,6 +27,7 @@ async function handler(req: NextApiRequest): CreateCollectionResponse { // 1. read file const { rawText, filename } = await readFileContentFromMongo({ teamId, + tmbId, bucketName: BucketNameEnum.dataset, fileId, isQAImport: true @@ -47,7 +48,7 @@ async function handler(req: NextApiRequest): CreateCollectionResponse { fileId, // special metadata - trainingType, + trainingType: DatasetCollectionDataProcessModeEnum.chunk, chunkSize: 0 } }); diff --git a/projects/app/src/pages/api/core/dataset/collection/create/fileId.ts b/projects/app/src/pages/api/core/dataset/collection/create/fileId.ts index 967c2ba3191e..2dcdc8ee5f2e 100644 --- a/projects/app/src/pages/api/core/dataset/collection/create/fileId.ts +++ b/projects/app/src/pages/api/core/dataset/collection/create/fileId.ts @@ -2,12 +2,8 @@ import { readFileContentFromMongo } from '@fastgpt/service/common/file/gridfs/co import { authDataset } from '@fastgpt/service/support/permission/dataset/auth'; import { FileIdCreateDatasetCollectionParams } from '@fastgpt/global/core/dataset/api'; import { createCollectionAndInsertData } from '@fastgpt/service/core/dataset/collection/controller'; -import { - DatasetCollectionTypeEnum, - TrainingModeEnum -} from '@fastgpt/global/core/dataset/constants'; +import { DatasetCollectionTypeEnum } from '@fastgpt/global/core/dataset/constants'; import { BucketNameEnum } from '@fastgpt/global/common/file/constants'; -import { hashStr } from '@fastgpt/global/common/string/tools'; import { MongoRawTextBuffer } from '@fastgpt/service/common/buffer/rawText/schema'; import { NextAPI } from '@/service/middleware/entry'; import { ApiRequestProps } from '@fastgpt/service/type/next'; @@ -17,14 +13,7 @@ import { CreateCollectionResponse } from '@/global/core/dataset/api'; async function handler( req: ApiRequestProps<FileIdCreateDatasetCollectionParams> ): CreateCollectionResponse { - const { - fileId, - trainingType = TrainingModeEnum.chunk, - chunkSize = 512, - chunkSplitter, - qaPrompt, - ...body - } = req.body; + const { fileId, customPdfParse, ...body } = req.body; const { teamId, tmbId, dataset } = await authDataset({ req, @@ -37,8 +26,10 @@ async function handler( // 1. read file const { rawText, filename } = await readFileContentFromMongo({ teamId, + tmbId, bucketName: BucketNameEnum.dataset, - fileId + fileId, + customPdfParse }); const { collectionId, insertResults } = await createCollectionAndInsertData({ @@ -54,12 +45,7 @@ async function handler( metadata: { relatedImgId: fileId }, - - // special metadata - trainingType, - chunkSize, - chunkSplitter, - qaPrompt + customPdfParse }, relatedId: fileId diff --git a/projects/app/src/pages/api/core/dataset/collection/create/link.ts b/projects/app/src/pages/api/core/dataset/collection/create/link.ts index db9eaf9d8db4..2c8800434392 100644 --- a/projects/app/src/pages/api/core/dataset/collection/create/link.ts +++ b/projects/app/src/pages/api/core/dataset/collection/create/link.ts @@ -13,14 +13,7 @@ import { urlsFetch } from '@fastgpt/service/common/string/cheerio'; import { hashStr } from '@fastgpt/global/common/string/tools'; async function handler(req: NextApiRequest): CreateCollectionResponse { - const { - link, - trainingType = TrainingModeEnum.chunk, - chunkSize = 512, - chunkSplitter, - qaPrompt, - ...body - } = req.body as LinkCreateDatasetCollectionParams; + const { link, ...body } = req.body as LinkCreateDatasetCollectionParams; const { teamId, tmbId, dataset } = await authDataset({ req, @@ -36,8 +29,8 @@ async function handler(req: NextApiRequest): CreateCollectionResponse { }); const { title = link, content = '' } = result[0]; - if (!content) { - return Promise.reject('Can not fetch content from link'); + if (!content || content === 'Cannot fetch internal url') { + return Promise.reject(content || 'Can not fetch content from link'); } const { collectionId, insertResults } = await createCollectionAndInsertData({ @@ -45,7 +38,7 @@ async function handler(req: NextApiRequest): CreateCollectionResponse { rawText: content, createCollectionParams: { ...body, - name: title, + name: title || link, teamId, tmbId, type: DatasetCollectionTypeEnum.link, @@ -53,12 +46,6 @@ async function handler(req: NextApiRequest): CreateCollectionResponse { relatedImgId: link, webPageSelector: body?.metadata?.webPageSelector }, - - trainingType, - chunkSize, - chunkSplitter, - qaPrompt, - rawLink: link }, diff --git a/projects/app/src/pages/api/core/dataset/collection/create/localFile.ts b/projects/app/src/pages/api/core/dataset/collection/create/localFile.ts index f8548f931d54..a8b8e1a1ab4c 100644 --- a/projects/app/src/pages/api/core/dataset/collection/create/localFile.ts +++ b/projects/app/src/pages/api/core/dataset/collection/create/localFile.ts @@ -6,7 +6,7 @@ import { FileCreateDatasetCollectionParams } from '@fastgpt/global/core/dataset/ import { removeFilesByPaths } from '@fastgpt/service/common/file/utils'; import { createCollectionAndInsertData } from '@fastgpt/service/core/dataset/collection/controller'; import { DatasetCollectionTypeEnum } from '@fastgpt/global/core/dataset/constants'; -import { getNanoid, hashStr } from '@fastgpt/global/common/string/tools'; +import { getNanoid } from '@fastgpt/global/common/string/tools'; import { BucketNameEnum } from '@fastgpt/global/common/file/constants'; import { readRawTextByLocalFile } from '@fastgpt/service/common/file/read/utils'; import { NextAPI } from '@/service/middleware/entry'; @@ -48,8 +48,10 @@ async function handler(req: NextApiRequest, res: NextApiResponse<any>): CreateCo // 1. read file const { rawText } = await readRawTextByLocalFile({ teamId, + tmbId, path: file.path, encoding: file.encoding, + customPdfParse: collectionData.customPdfParse, metadata: { ...fileMetadata, relatedId: relatedImgId diff --git a/projects/app/src/pages/api/core/dataset/collection/create/reTrainingCollection.ts b/projects/app/src/pages/api/core/dataset/collection/create/reTrainingCollection.ts index 6d0b1a41f7bc..9671d9912151 100644 --- a/projects/app/src/pages/api/core/dataset/collection/create/reTrainingCollection.ts +++ b/projects/app/src/pages/api/core/dataset/collection/create/reTrainingCollection.ts @@ -10,7 +10,7 @@ import { hashStr } from '@fastgpt/global/common/string/tools'; import { readDatasetSourceRawText } from '@fastgpt/service/core/dataset/read'; import { NextAPI } from '@/service/middleware/entry'; import { ApiRequestProps } from '@fastgpt/service/type/next'; -import { delOnlyCollection } from '@fastgpt/service/core/dataset/collection/controller'; +import { delCollection } from '@fastgpt/service/core/dataset/collection/controller'; import { authDatasetCollection } from '@fastgpt/service/support/permission/dataset/auth'; import { CommonErrEnum } from '@fastgpt/global/common/error/code/common'; import { ReadPermissionVal } from '@fastgpt/global/support/permission/constant'; @@ -24,20 +24,14 @@ type RetrainingCollectionResponse = { async function handler( req: ApiRequestProps<reTrainingDatasetFileCollectionParams> ): Promise<RetrainingCollectionResponse> { - const { - collectionId, - trainingType = TrainingModeEnum.chunk, - chunkSize = 512, - chunkSplitter, - qaPrompt - } = req.body; + const { collectionId, customPdfParse, ...data } = req.body; if (!collectionId) { return Promise.reject(CommonErrEnum.missingParams); } // 凭证校验 - const { collection } = await authDatasetCollection({ + const { collection, teamId, tmbId } = await authDatasetCollection({ req, authToken: true, authApiKey: true, @@ -84,21 +78,33 @@ async function handler( })(); const rawText = await readDatasetSourceRawText({ - teamId: collection.teamId, + teamId, + tmbId, + customPdfParse, ...sourceReadType }); return mongoSessionRun(async (session) => { + await delCollection({ + collections: [collection], + session, + delImg: false, + delFile: false + }); + const { collectionId } = await createCollectionAndInsertData({ dataset: collection.dataset, rawText, createCollectionParams: { + ...data, teamId: collection.teamId, tmbId: collection.tmbId, datasetId: collection.dataset._id, name: collection.name, type: collection.type, + customPdfParse, + fileId: collection.fileId, rawLink: collection.rawLink, externalFileId: collection.externalFileId, @@ -114,17 +120,9 @@ async function handler( parentId: collection.parentId, // special metadata - trainingType, - chunkSize, - chunkSplitter, - qaPrompt, metadata: collection.metadata } }); - await delOnlyCollection({ - collections: [collection], - session - }); return { collectionId }; }); diff --git a/projects/app/src/pages/api/core/dataset/collection/create/text.ts b/projects/app/src/pages/api/core/dataset/collection/create/text.ts index ec3af225d838..c77b50d57c31 100644 --- a/projects/app/src/pages/api/core/dataset/collection/create/text.ts +++ b/projects/app/src/pages/api/core/dataset/collection/create/text.ts @@ -2,25 +2,13 @@ import type { NextApiRequest } from 'next'; import type { TextCreateDatasetCollectionParams } from '@fastgpt/global/core/dataset/api.d'; import { authDataset } from '@fastgpt/service/support/permission/dataset/auth'; import { createCollectionAndInsertData } from '@fastgpt/service/core/dataset/collection/controller'; -import { - TrainingModeEnum, - DatasetCollectionTypeEnum -} from '@fastgpt/global/core/dataset/constants'; -import { hashStr } from '@fastgpt/global/common/string/tools'; +import { DatasetCollectionTypeEnum } from '@fastgpt/global/core/dataset/constants'; import { NextAPI } from '@/service/middleware/entry'; import { WritePermissionVal } from '@fastgpt/global/support/permission/constant'; import { CreateCollectionResponse } from '@/global/core/dataset/api'; async function handler(req: NextApiRequest): CreateCollectionResponse { - const { - name, - text, - trainingType = TrainingModeEnum.chunk, - chunkSize = 512, - chunkSplitter, - qaPrompt, - ...body - } = req.body as TextCreateDatasetCollectionParams; + const { name, text, ...body } = req.body as TextCreateDatasetCollectionParams; const { teamId, tmbId, dataset } = await authDataset({ req, @@ -39,11 +27,7 @@ async function handler(req: NextApiRequest): CreateCollectionResponse { tmbId, type: DatasetCollectionTypeEnum.virtual, - name, - trainingType, - chunkSize, - chunkSplitter, - qaPrompt + name } }); diff --git a/projects/app/src/pages/api/core/dataset/collection/delete.ts b/projects/app/src/pages/api/core/dataset/collection/delete.ts index 6b84213e432d..c52006820c0d 100644 --- a/projects/app/src/pages/api/core/dataset/collection/delete.ts +++ b/projects/app/src/pages/api/core/dataset/collection/delete.ts @@ -34,7 +34,8 @@ async function handler(req: NextApiRequest) { await mongoSessionRun((session) => delCollection({ collections, - delRelatedSource: true, + delImg: true, + delFile: true, session }) ); diff --git a/projects/app/src/pages/api/core/dataset/collection/listV2.ts b/projects/app/src/pages/api/core/dataset/collection/listV2.ts index 535af97aaf6c..b92e02d56a9e 100644 --- a/projects/app/src/pages/api/core/dataset/collection/listV2.ts +++ b/projects/app/src/pages/api/core/dataset/collection/listV2.ts @@ -14,6 +14,7 @@ import { PaginationResponse } from '@fastgpt/web/common/fetch/type'; import { parsePaginationRequest } from '@fastgpt/service/common/api/pagination'; import { DatasetCollectionSchemaType } from '@fastgpt/global/core/dataset/type'; import { MongoDatasetData } from '@fastgpt/service/core/dataset/data/schema'; +import { MongoDatasetTraining } from '@fastgpt/service/core/dataset/training/schema'; async function handler( req: NextApiRequest @@ -115,7 +116,7 @@ async function handler( { _id: string; count: number }[], { _id: string; count: number }[] ] = await Promise.all([ - MongoDatasetCollection.aggregate( + MongoDatasetTraining.aggregate( [ { $match: { diff --git a/projects/app/src/pages/api/core/dataset/create.ts b/projects/app/src/pages/api/core/dataset/create.ts index 1bf40c2c3ccf..40e4b5ef2077 100644 --- a/projects/app/src/pages/api/core/dataset/create.ts +++ b/projects/app/src/pages/api/core/dataset/create.ts @@ -6,12 +6,12 @@ import { getLLMModel, getEmbeddingModel, getDatasetModel, - getDefaultEmbeddingModel + getDefaultEmbeddingModel, + getVlmModel } from '@fastgpt/service/core/ai/model'; import { checkTeamDatasetLimit } from '@fastgpt/service/support/permission/teamLimit'; import { WritePermissionVal } from '@fastgpt/global/support/permission/constant'; import { NextAPI } from '@/service/middleware/entry'; -import { DatasetErrEnum } from '@fastgpt/global/common/error/code/dataset'; import type { ApiRequestProps } from '@fastgpt/service/type/next'; import { parseParentIdInMongo } from '@fastgpt/global/common/parentFolder/utils'; import { authDataset } from '@fastgpt/service/support/permission/dataset/auth'; @@ -32,8 +32,9 @@ async function handler( intro, type = DatasetTypeEnum.dataset, avatar, - vectorModel = getDefaultEmbeddingModel().model, - agentModel = getDatasetModel().model, + vectorModel = getDefaultEmbeddingModel()?.model, + agentModel = getDatasetModel()?.model, + vlmModel, apiServer, feishuServer, yuqueServer @@ -63,8 +64,11 @@ async function handler( // check model valid const vectorModelStore = getEmbeddingModel(vectorModel); const agentModelStore = getLLMModel(agentModel); - if (!vectorModelStore || !agentModelStore) { - return Promise.reject(DatasetErrEnum.invalidVectorModelOrQAModel); + if (!vectorModelStore) { + return Promise.reject(`System not embedding model`); + } + if (!agentModelStore) { + return Promise.reject(`System not llm model`); } // check limit @@ -81,6 +85,7 @@ async function handler( tmbId, vectorModel, agentModel, + vlmModel, avatar, type, apiServer, @@ -88,7 +93,7 @@ async function handler( yuqueServer } ], - { session } + { session, ordered: true } ); await refreshSourceAvatar(avatar, undefined, session); diff --git a/projects/app/src/pages/api/core/dataset/data/pushData.ts b/projects/app/src/pages/api/core/dataset/data/pushData.ts index 2df6223534d8..de0a8f000225 100644 --- a/projects/app/src/pages/api/core/dataset/data/pushData.ts +++ b/projects/app/src/pages/api/core/dataset/data/pushData.ts @@ -7,9 +7,13 @@ import { predictDataLimitLength } from '@fastgpt/global/core/dataset/utils'; import { pushDataListToTrainingQueue } from '@fastgpt/service/core/dataset/training/controller'; import { NextAPI } from '@/service/middleware/entry'; import { WritePermissionVal } from '@fastgpt/global/support/permission/constant'; +import { getTrainingModeByCollection } from '@fastgpt/service/core/dataset/collection/utils'; async function handler(req: NextApiRequest, res: NextApiResponse<any>) { const body = req.body as PushDatasetDataProps; + // Adapter 4.9.0 + body.trainingType = body.trainingType || body.trainingMode; + const { collectionId, data } = body; if (!collectionId || !Array.isArray(data)) { @@ -32,7 +36,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse<any>) { // auth dataset limit await checkDatasetLimit({ teamId, - insertLen: predictDataLimitLength(collection.trainingType, data) + insertLen: predictDataLimitLength(getTrainingModeByCollection(collection), data) }); return pushDataListToTrainingQueue({ @@ -40,8 +44,9 @@ async function handler(req: NextApiRequest, res: NextApiResponse<any>) { teamId, tmbId, datasetId: collection.datasetId, + vectorModel: collection.dataset.vectorModel, agentModel: collection.dataset.agentModel, - vectorModel: collection.dataset.vectorModel + vlmModel: collection.dataset.vlmModel }); } diff --git a/projects/app/src/pages/api/core/dataset/delete.ts b/projects/app/src/pages/api/core/dataset/delete.ts index 4e7c47388767..9f4edae54564 100644 --- a/projects/app/src/pages/api/core/dataset/delete.ts +++ b/projects/app/src/pages/api/core/dataset/delete.ts @@ -34,17 +34,17 @@ async function handler(req: NextApiRequest) { }); const datasetIds = datasets.map((d) => d._id); + // delete collection.tags + await MongoDatasetCollectionTags.deleteMany({ + teamId, + datasetId: { $in: datasetIds } + }); + // delete all dataset.data and pg data await mongoSessionRun(async (session) => { // delete dataset data await delDatasetRelevantData({ datasets, session }); - // delete collection.tags - await MongoDatasetCollectionTags.deleteMany({ - teamId, - datasetId: { $in: datasetIds } - }).session(session); - // delete dataset await MongoDataset.deleteMany( { diff --git a/projects/app/src/pages/api/core/dataset/detail.ts b/projects/app/src/pages/api/core/dataset/detail.ts index 6f96d7404460..f1e17e4e8fda 100644 --- a/projects/app/src/pages/api/core/dataset/detail.ts +++ b/projects/app/src/pages/api/core/dataset/detail.ts @@ -1,4 +1,4 @@ -import { getLLMModel, getEmbeddingModel } from '@fastgpt/service/core/ai/model'; +import { getLLMModel, getEmbeddingModel, getVlmModel } from '@fastgpt/service/core/ai/model'; import { authDataset } from '@fastgpt/service/support/permission/dataset/auth'; import { ReadPermissionVal } from '@fastgpt/global/support/permission/constant'; import { NextAPI } from '@/service/middleware/entry'; @@ -51,7 +51,8 @@ async function handler(req: ApiRequestProps<Query>): Promise<DatasetItemType> { : undefined, permission, vectorModel: getEmbeddingModel(dataset.vectorModel), - agentModel: getLLMModel(dataset.agentModel) + agentModel: getLLMModel(dataset.agentModel), + vlmModel: getVlmModel(dataset.vlmModel) }; } diff --git a/projects/app/src/pages/api/core/dataset/file/getPreviewChunks.ts b/projects/app/src/pages/api/core/dataset/file/getPreviewChunks.ts index d1d22b3fac4e..690c016a373c 100644 --- a/projects/app/src/pages/api/core/dataset/file/getPreviewChunks.ts +++ b/projects/app/src/pages/api/core/dataset/file/getPreviewChunks.ts @@ -17,6 +17,7 @@ export type PostPreviewFilesChunksProps = { chunkSize: number; overlapRatio: number; customSplitChar?: string; + customPdfParse?: boolean; // Read params selector?: string; @@ -40,7 +41,8 @@ async function handler( selector, isQAImport, datasetId, - externalFileId + externalFileId, + customPdfParse = false } = req.body; if (!sourceId) { @@ -50,7 +52,7 @@ async function handler( throw new Error('chunkSize is too large, should be less than 30000'); } - const { teamId, apiServer, feishuServer, yuqueServer } = await (async () => { + const { teamId, tmbId, apiServer, feishuServer, yuqueServer } = await (async () => { if (type === DatasetSourceReadTypeEnum.fileLocal) { const res = await authCollectionFile({ req, @@ -60,10 +62,11 @@ async function handler( per: OwnerPermissionVal }); return { - teamId: res.teamId + teamId: res.teamId, + tmbId: res.tmbId }; } - const { dataset } = await authDataset({ + const { dataset, teamId, tmbId } = await authDataset({ req, authApiKey: true, authToken: true, @@ -71,7 +74,8 @@ async function handler( per: WritePermissionVal }); return { - teamId: dataset.teamId, + teamId, + tmbId, apiServer: dataset.apiServer, feishuServer: dataset.feishuServer, yuqueServer: dataset.yuqueServer @@ -80,6 +84,7 @@ async function handler( const rawText = await readDatasetSourceRawText({ teamId, + tmbId, type, sourceId, selector, @@ -87,7 +92,8 @@ async function handler( apiServer, feishuServer, yuqueServer, - externalFileId + externalFileId, + customPdfParse }); return rawText2Chunks({ @@ -96,6 +102,6 @@ async function handler( overlapRatio, customReg: customSplitChar ? [customSplitChar] : [], isQAImport: isQAImport - }).slice(0, 15); + }).slice(0, 10); } export default NextAPI(handler); diff --git a/projects/app/src/pages/api/core/dataset/folder/create.ts b/projects/app/src/pages/api/core/dataset/folder/create.ts index 5f9a7de2671b..1173671312ee 100644 --- a/projects/app/src/pages/api/core/dataset/folder/create.ts +++ b/projects/app/src/pages/api/core/dataset/folder/create.ts @@ -87,7 +87,7 @@ async function handler( permission: OwnerPermissionVal } ], - { session } + { session, ordered: true } ); } }); diff --git a/projects/app/src/pages/api/core/dataset/training/rebuildEmbedding.ts b/projects/app/src/pages/api/core/dataset/training/rebuildEmbedding.ts index 063ae04a1f52..851547447a64 100644 --- a/projects/app/src/pages/api/core/dataset/training/rebuildEmbedding.ts +++ b/projects/app/src/pages/api/core/dataset/training/rebuildEmbedding.ts @@ -6,7 +6,7 @@ import { MongoDatasetData } from '@fastgpt/service/core/dataset/data/schema'; import { MongoDatasetTraining } from '@fastgpt/service/core/dataset/training/schema'; import { createTrainingUsage } from '@fastgpt/service/support/wallet/usage/controller'; import { UsageSourceEnum } from '@fastgpt/global/support/wallet/usage/constants'; -import { getLLMModel, getEmbeddingModel } from '@fastgpt/service/core/ai/model'; +import { getLLMModel, getEmbeddingModel, getVlmModel } from '@fastgpt/service/core/ai/model'; import { TrainingModeEnum } from '@fastgpt/global/core/dataset/constants'; import { ApiRequestProps } from '@fastgpt/service/type/next'; import { OwnerPermissionVal } from '@fastgpt/global/support/permission/constant'; @@ -50,7 +50,8 @@ async function handler(req: ApiRequestProps<rebuildEmbeddingBody>): Promise<Resp appName: '切换索引模型', billSource: UsageSourceEnum.training, vectorModel: getEmbeddingModel(dataset.vectorModel)?.name, - agentModel: getLLMModel(dataset.agentModel)?.name + agentModel: getLLMModel(dataset.agentModel)?.name, + vllmModel: getVlmModel(dataset.vlmModel)?.name }); // update vector model and dataset.data rebuild field @@ -122,7 +123,8 @@ async function handler(req: ApiRequestProps<rebuildEmbeddingBody>): Promise<Resp } ], { - session + session, + ordered: true } ); } diff --git a/projects/app/src/pages/api/core/dataset/update.ts b/projects/app/src/pages/api/core/dataset/update.ts index f20eeeb6ec6b..2ebff1121378 100644 --- a/projects/app/src/pages/api/core/dataset/update.ts +++ b/projects/app/src/pages/api/core/dataset/update.ts @@ -56,6 +56,7 @@ async function handler( avatar, intro, agentModel, + vlmModel, websiteConfig, externalReadUrl, apiServer, @@ -109,7 +110,7 @@ async function handler( updateTraining({ teamId: dataset.teamId, datasetId: id, - agentModel: agentModel?.model + agentModel }); const onUpdate = async (session: ClientSession) => { @@ -119,7 +120,8 @@ async function handler( ...parseParentIdInMongo(parentId), ...(name && { name }), ...(avatar && { avatar }), - ...(agentModel && { agentModel: agentModel.model }), + ...(agentModel && { agentModel }), + ...(vlmModel && { vlmModel }), ...(websiteConfig && { websiteConfig }), ...(status && { status }), ...(intro !== undefined && { intro }), @@ -212,7 +214,7 @@ const updateTraining = async ({ $set: { model: agentModel, retryCount: 5, - lockTime: new Date() + lockTime: new Date('2000/1/1') } } ); diff --git a/projects/app/src/pages/api/support/outLink/delete.ts b/projects/app/src/pages/api/support/outLink/delete.ts index 343678df3842..e220a0c01544 100644 --- a/projects/app/src/pages/api/support/outLink/delete.ts +++ b/projects/app/src/pages/api/support/outLink/delete.ts @@ -16,7 +16,7 @@ async function handler( ): Promise<OutLinkDeleteResponse> { const { id } = req.query; await authOutLinkCrud({ req, outLinkId: id, authToken: true, per: OwnerPermissionVal }); - await MongoOutLink.findByIdAndRemove(id); + await MongoOutLink.findByIdAndDelete(id); return {}; } diff --git a/projects/app/src/pages/api/support/outLink/list.test.ts b/projects/app/src/pages/api/support/outLink/list.test.ts deleted file mode 100644 index 4afd4e99f657..000000000000 --- a/projects/app/src/pages/api/support/outLink/list.test.ts +++ /dev/null @@ -1,67 +0,0 @@ -import '../../__mocks__/base'; -import { root } from '../../__mocks__/db/init'; -import { getTestRequest } from '@/test/utils'; -import type { OutLinkListQuery } from './list'; -import { AppErrEnum } from '@fastgpt/global/common/error/code/app'; -import handler from './list'; -import { MongoOutLink } from '@fastgpt/service/support/outLink/schema'; - -beforeAll(async () => { - await MongoOutLink.create({ - shareId: 'aaa', - appId: root.appId, - tmbId: root.tmbId, - teamId: root.teamId, - type: 'share', - name: 'aaa' - }); - await MongoOutLink.create({ - shareId: 'bbb', - appId: root.appId, - tmbId: root.tmbId, - teamId: root.teamId, - type: 'share', - name: 'bbb' - }); -}); - -test('Should return a list of outLink', async () => { - const res = (await handler( - ...getTestRequest<OutLinkListQuery>({ - query: { - appId: root.appId, - type: 'share' - }, - user: root - }) - )) as any; - - expect(res.code).toBe(200); - expect(res.data.length).toBe(2); -}); - -test('appId is required', async () => { - const res = (await handler( - ...getTestRequest<OutLinkListQuery>({ - query: { - type: 'share' - }, - user: root - }) - )) as any; - expect(res.code).toBe(500); - expect(res.error).toBe(AppErrEnum.unExist); -}); - -test('if type is not provided, return nothing', async () => { - const res = (await handler( - ...getTestRequest<OutLinkListQuery>({ - query: { - appId: root.appId - }, - user: root - }) - )) as any; - expect(res.code).toBe(200); - expect(res.data.length).toBe(0); -}); diff --git a/projects/app/src/pages/api/support/outLink/update.test.ts b/projects/app/src/pages/api/support/outLink/update.test.ts deleted file mode 100644 index 650d33a30a54..000000000000 --- a/projects/app/src/pages/api/support/outLink/update.test.ts +++ /dev/null @@ -1,54 +0,0 @@ -import '../../__mocks__/base'; -import { getTestRequest } from '@/test/utils'; -import handler, { OutLinkUpdateBody, OutLinkUpdateQuery } from './update'; -import { MongoOutLink } from '@fastgpt/service/support/outLink/schema'; -import { CommonErrEnum } from '@fastgpt/global/common/error/code/common'; -import { root } from '../../__mocks__/db/init'; - -beforeAll(async () => { - await MongoOutLink.create({ - shareId: 'aaa', - appId: root.appId, - tmbId: root.tmbId, - teamId: root.teamId, - type: 'share', - name: 'aaa' - }); -}); - -test('Update Outlink', async () => { - const outlink = await MongoOutLink.findOne({ name: 'aaa' }).lean(); - if (!outlink) { - throw new Error('Outlink not found'); - } - - const res = (await handler( - ...getTestRequest<OutLinkUpdateQuery, OutLinkUpdateBody>({ - body: { - _id: outlink._id, - name: 'changed' - }, - user: root - }) - )) as any; - - console.log(res); - expect(res.code).toBe(200); - - const link = await MongoOutLink.findById(outlink._id).lean(); - expect(link?.name).toBe('changed'); -}); - -test('Did not post _id', async () => { - const res = (await handler( - ...getTestRequest<OutLinkUpdateQuery, OutLinkUpdateBody>({ - body: { - name: 'changed' - }, - user: root - }) - )) as any; - - expect(res.code).toBe(500); - expect(res.error).toBe(CommonErrEnum.missingParams); -}); diff --git a/projects/app/src/pages/api/support/wallet/usage/createTrainingUsage.ts b/projects/app/src/pages/api/support/wallet/usage/createTrainingUsage.ts index 3b96351a37ca..47dafcf42e14 100644 --- a/projects/app/src/pages/api/support/wallet/usage/createTrainingUsage.ts +++ b/projects/app/src/pages/api/support/wallet/usage/createTrainingUsage.ts @@ -1,7 +1,7 @@ -import type { NextApiRequest, NextApiResponse } from 'next'; +import type { NextApiRequest } from 'next'; import { UsageSourceEnum } from '@fastgpt/global/support/wallet/usage/constants'; import { CreateTrainingUsageProps } from '@fastgpt/global/support/wallet/usage/api.d'; -import { getLLMModel, getEmbeddingModel } from '@fastgpt/service/core/ai/model'; +import { getLLMModel, getEmbeddingModel, getVlmModel } from '@fastgpt/service/core/ai/model'; import { createTrainingUsage } from '@fastgpt/service/support/wallet/usage/controller'; import { authDataset } from '@fastgpt/service/support/permission/dataset/auth'; import { WritePermissionVal } from '@fastgpt/global/support/permission/constant'; @@ -24,7 +24,8 @@ async function handler(req: NextApiRequest) { appName: name, billSource: UsageSourceEnum.training, vectorModel: getEmbeddingModel(dataset.vectorModel).name, - agentModel: getLLMModel(dataset.agentModel).name + agentModel: getLLMModel(dataset.agentModel).name, + vllmModel: getVlmModel(dataset.vlmModel)?.name }); return billId; diff --git a/projects/app/src/pages/app/list/index.tsx b/projects/app/src/pages/app/list/index.tsx index 86d7da1ee18b..274909c261d2 100644 --- a/projects/app/src/pages/app/list/index.tsx +++ b/projects/app/src/pages/app/list/index.tsx @@ -218,7 +218,7 @@ const MyApps = () => { size="md" Button={ <Button variant={'primary'} leftIcon={<AddIcon />}> - <Box>{t('common:common.Create New')}</Box> + <Box>{t('common:new_create')}</Box> </Button> } menuList={[ diff --git a/projects/app/src/pages/dataset/list/index.tsx b/projects/app/src/pages/dataset/list/index.tsx index f50cd52ba99c..fe2c2dd5c822 100644 --- a/projects/app/src/pages/dataset/list/index.tsx +++ b/projects/app/src/pages/dataset/list/index.tsx @@ -147,7 +147,7 @@ const Dataset = () => { <Button variant={'primary'} px="0"> <Flex alignItems={'center'} px={5}> <AddIcon mr={2} /> - <Box>{t('common:common.Create New')}</Box> + <Box>{t('common:new_create')}</Box> </Flex> </Button> } diff --git a/projects/app/src/service/common/system/cronTask.ts b/projects/app/src/service/common/system/cronTask.ts index d12dd1d7b5e1..bba0b348bfab 100644 --- a/projects/app/src/service/common/system/cronTask.ts +++ b/projects/app/src/service/common/system/cronTask.ts @@ -1,15 +1,16 @@ import { BucketNameEnum } from '@fastgpt/global/common/file/constants'; +import { retryFn } from '@fastgpt/global/common/system/utils'; import { delFileByFileIdList, getGFSCollection } from '@fastgpt/service/common/file/gridfs/controller'; -import { mongoSessionRun } from '@fastgpt/service/common/mongo/sessionRun'; import { addLog } from '@fastgpt/service/common/system/log'; import { deleteDatasetDataVector, getVectorDataByTime } from '@fastgpt/service/common/vectorStore/controller'; import { MongoDatasetCollection } from '@fastgpt/service/core/dataset/collection/schema'; +import { MongoDatasetDataText } from '@fastgpt/service/core/dataset/data/dataTextSchema'; import { MongoDatasetData } from '@fastgpt/service/core/dataset/data/schema'; import { MongoDatasetTraining } from '@fastgpt/service/core/dataset/training/schema'; import { addDays } from 'date-fns'; @@ -129,32 +130,35 @@ export async function checkInvalidDatasetData(start: Date, end: Date) { for await (const item of list) { try { // 3. 查看该collection是否存在,不存在,则删除对应的数据 - const collection = await MongoDatasetCollection.findOne({ _id: item.collectionId }); + const collection = await MongoDatasetCollection.findOne( + { _id: item.collectionId }, + '_id' + ).lean(); if (!collection) { - await mongoSessionRun(async (session) => { - await MongoDatasetTraining.deleteMany( - { - teamId: item.teamId, - collectionId: item.collectionId - }, - { session } - ); - await MongoDatasetData.deleteMany( - { - teamId: item.teamId, - collectionId: item.collectionId - }, - { session } - ); + console.log('collection is not found', item); + + await retryFn(async () => { + await MongoDatasetTraining.deleteMany({ + teamId: item.teamId, + datasetId: item.datasetId, + collectionId: item.collectionId + }); + await MongoDatasetDataText.deleteMany({ + teamId: item.teamId, + datasetId: item.datasetId, + collectionId: item.collectionId + }); await deleteDatasetDataVector({ teamId: item.teamId, datasetIds: [item.datasetId], collectionIds: [item.collectionId] }); + await MongoDatasetData.deleteMany({ + teamId: item.teamId, + datasetId: item.datasetId, + collectionId: item.collectionId + }); }); - - console.log('collection is not found', item); - continue; } } catch (error) {} if (++index % 100 === 0) { diff --git a/projects/app/src/service/common/system/index.ts b/projects/app/src/service/common/system/index.ts index 83337c470a1c..cb5c2de89381 100644 --- a/projects/app/src/service/common/system/index.ts +++ b/projects/app/src/service/common/system/index.ts @@ -83,7 +83,8 @@ export async function initSystemConfig() { ...fileRes?.feConfigs, ...defaultFeConfigs, ...(dbConfig.feConfigs || {}), - isPlus: !!FastGPTProUrl + isPlus: !!FastGPTProUrl, + show_aiproxy: !!process.env.AIPROXY_API_ENDPOINT }, systemEnv: { ...fileRes.systemEnv, diff --git a/projects/app/src/service/core/dataset/data/controller.ts b/projects/app/src/service/core/dataset/data/controller.ts index aec06b2e71e6..ae77ce77e0f4 100644 --- a/projects/app/src/service/core/dataset/data/controller.ts +++ b/projects/app/src/service/core/dataset/data/controller.ts @@ -8,12 +8,60 @@ import { insertDatasetDataVector } from '@fastgpt/service/common/vectorStore/con import { getDefaultIndex } from '@fastgpt/global/core/dataset/utils'; import { jiebaSplit } from '@fastgpt/service/common/string/jieba'; import { deleteDatasetDataVector } from '@fastgpt/service/common/vectorStore/controller'; -import { DatasetDataItemType } from '@fastgpt/global/core/dataset/type'; +import { DatasetDataIndexItemType, DatasetDataItemType } from '@fastgpt/global/core/dataset/type'; import { getEmbeddingModel } from '@fastgpt/service/core/ai/model'; import { mongoSessionRun } from '@fastgpt/service/common/mongo/sessionRun'; import { ClientSession } from '@fastgpt/service/common/mongo'; import { MongoDatasetDataText } from '@fastgpt/service/core/dataset/data/dataTextSchema'; +import { DatasetDataIndexTypeEnum } from '@fastgpt/global/core/dataset/data/constants'; +const formatIndexes = ({ + indexes, + q, + a = '' +}: { + indexes?: (Omit<DatasetDataIndexItemType, 'dataId'> & { dataId?: string })[]; + q: string; + a?: string; +}) => { + indexes = indexes || []; + // If index not type, set it to custom + indexes = indexes + .map((item) => ({ + text: typeof item.text === 'string' ? item.text : String(item.text), + type: item.type || DatasetDataIndexTypeEnum.custom, + dataId: item.dataId + })) + .filter((item) => !!item.text.trim()); + + // Recompute default indexes, Merge ids of the same index, reduce the number of rebuilds + const defaultIndexes = getDefaultIndex({ q, a }); + const concatDefaultIndexes = defaultIndexes.map((item) => { + const oldIndex = indexes!.find((index) => index.text === item.text); + if (oldIndex) { + return { + type: DatasetDataIndexTypeEnum.default, + text: item.text, + dataId: oldIndex.dataId + }; + } else { + return item; + } + }); + indexes = indexes.filter((item) => item.type !== DatasetDataIndexTypeEnum.default); + indexes.push(...concatDefaultIndexes); + + // Filter same text + indexes = indexes.filter( + (item, index, self) => index === self.findIndex((t) => t.text === item.text) + ); + + return indexes.map((index) => ({ + type: index.type, + text: index.text, + dataId: index.dataId + })); +}; /* insert data. * 1. create data id * 2. insert pg @@ -41,42 +89,28 @@ export async function insertData2Dataset({ return Promise.reject("teamId and tmbId can't be the same"); } - const qaStr = getDefaultIndex({ q, a }).text; - // 1. Get vector indexes and insert // Empty indexes check, if empty, create default index - indexes = - Array.isArray(indexes) && indexes.length > 0 - ? indexes.map((index) => ({ - text: index.text, - dataId: undefined, - defaultIndex: index.text.trim() === qaStr - })) - : [getDefaultIndex({ q, a })]; - - if (!indexes.find((index) => index.defaultIndex)) { - indexes.unshift(getDefaultIndex({ q, a })); - } else if (q && a && !indexes.find((index) => index.text === q)) { - // push a q index - indexes.push({ - defaultIndex: false, - text: q - }); - } - - indexes = indexes.slice(0, 6); + const newIndexes = formatIndexes({ indexes, q, a }); // insert to vector store const result = await Promise.all( - indexes.map((item) => - insertDatasetDataVector({ + newIndexes.map(async (item) => { + const result = await insertDatasetDataVector({ query: item.text, model: getEmbeddingModel(model), teamId, datasetId, collectionId - }) - ) + }); + return { + tokens: result.tokens, + index: { + ...item, + dataId: result.insertId + } + }; + }) ); // 2. Create mongo data @@ -89,16 +123,11 @@ export async function insertData2Dataset({ collectionId, q, a, - // FullText tmp - // fullTextToken: jiebaSplit({ text: qaStr }), chunkIndex, - indexes: indexes?.map((item, i) => ({ - ...item, - dataId: result[i].insertId - })) + indexes: result.map((item) => item.index) } ], - { session } + { session, ordered: true } ); // 3. Create mongo data text @@ -109,10 +138,10 @@ export async function insertData2Dataset({ datasetId, collectionId, dataId: _id, - fullTextToken: jiebaSplit({ text: qaStr }) + fullTextToken: jiebaSplit({ text: `${q}\n${a}`.trim() }) } ], - { session } + { session, ordered: true } ); return { @@ -122,7 +151,7 @@ export async function insertData2Dataset({ } /** - * update data + * Update data(indexes overwrite) * 1. compare indexes * 2. insert new pg data * session run: @@ -139,30 +168,19 @@ export async function updateData2Dataset({ if (!Array.isArray(indexes)) { return Promise.reject('indexes is required'); } - const qaStr = getDefaultIndex({ q, a }).text; - // patch index and update pg + // 1. Get mongo data const mongoData = await MongoDatasetData.findById(dataId); if (!mongoData) return Promise.reject('core.dataset.error.Data not found'); - // remove defaultIndex - let formatIndexes = indexes.map((index) => ({ - ...index, - text: index.text.trim(), - defaultIndex: index.text.trim() === qaStr - })); - if (!formatIndexes.find((index) => index.defaultIndex)) { - const defaultIndex = mongoData.indexes.find((index) => index.defaultIndex); - formatIndexes.unshift(defaultIndex ? defaultIndex : getDefaultIndex({ q, a })); - } - formatIndexes = formatIndexes.slice(0, 6); + // 2. Compute indexes + const formatIndexesResult = formatIndexes({ indexes, q, a }); - // patch indexes, create, update, delete + // 3. Patch indexes, create, update, delete const patchResult: PatchIndexesProps[] = []; - // find database indexes in new Indexes, if have not, delete it for (const item of mongoData.indexes) { - const index = formatIndexes.find((index) => index.dataId === item.dataId); + const index = formatIndexesResult.find((index) => index.dataId === item.dataId); if (!index) { patchResult.push({ type: 'delete', @@ -170,53 +188,48 @@ export async function updateData2Dataset({ }); } } - for (const item of formatIndexes) { - const index = mongoData.indexes.find((index) => index.dataId === item.dataId); - // in database, update - if (index) { - // default index update - if (index.defaultIndex && index.text !== qaStr) { + for (const item of formatIndexesResult) { + if (!item.dataId) { + patchResult.push({ + type: 'create', + index: item + }); + } else { + const index = mongoData.indexes.find((index) => index.dataId === item.dataId); + if (!index) continue; + + // Not change + if (index.text === item.text) { patchResult.push({ - type: 'update', + type: 'unChange', index: { - //@ts-ignore - ...index.toObject(), - text: qaStr + ...item, + dataId: index.dataId } }); - continue; - } - // custom index update - if (index.text !== item.text) { + } else { + // index Update patchResult.push({ type: 'update', - index: item + index: { + ...item, + dataId: index.dataId + } }); - continue; } - patchResult.push({ - type: 'unChange', - index: item - }); - } else { - // not in database, create - patchResult.push({ - type: 'create', - index: item - }); } } - // update mongo updateTime + // 4. Update mongo updateTime(便于脏数据检查器识别) mongoData.updateTime = new Date(); await mongoData.save(); - // insert vector - const clonePatchResult2Insert: PatchIndexesProps[] = JSON.parse(JSON.stringify(patchResult)); + // 5. Insert vector const insertResult = await Promise.all( - clonePatchResult2Insert.map(async (item) => { - // insert new vector and update dateId - if (item.type === 'create' || item.type === 'update') { + patchResult + .filter((item) => item.type === 'create' || item.type === 'update') + .map(async (item) => { + // insert new vector and update dateId const result = await insertDatasetDataVector({ query: item.index.text, model: getEmbeddingModel(model), @@ -225,26 +238,22 @@ export async function updateData2Dataset({ collectionId: mongoData.collectionId }); item.index.dataId = result.insertId; - return result; - } - return { - tokens: 0 - }; - }) + return { + tokens: result.tokens + }; + }) ); const tokens = insertResult.reduce((acc, cur) => acc + cur.tokens, 0); + + const newIndexes = patchResult + .filter((item) => item.type !== 'delete') + .map((item) => item.index) as DatasetDataIndexItemType[]; + // console.log(clonePatchResult2Insert); await mongoSessionRun(async (session) => { - // update mongo - const newIndexes = clonePatchResult2Insert - .filter((item) => item.type !== 'delete') - .map((item) => item.index); - // update mongo other data + // Update MongoData mongoData.q = q || mongoData.q; mongoData.a = a ?? mongoData.a; - // FullText tmp - // mongoData.fullTextToken = jiebaSplit({ text: `${mongoData.q}\n${mongoData.a}`.trim() }); - // @ts-ignore mongoData.indexes = newIndexes; await mongoData.save({ session }); @@ -255,15 +264,15 @@ export async function updateData2Dataset({ { session } ); - // delete vector + // Delete vector const deleteIdList = patchResult .filter((item) => item.type === 'delete' || item.type === 'update') .map((item) => item.index.dataId) - .filter(Boolean); + .filter(Boolean) as string[]; if (deleteIdList.length > 0) { await deleteDatasetDataVector({ teamId: mongoData.teamId, - idList: deleteIdList as string[] + idList: deleteIdList }); } }); @@ -275,7 +284,8 @@ export async function updateData2Dataset({ export const deleteDatasetData = async (data: DatasetDataItemType) => { await mongoSessionRun(async (session) => { - await MongoDatasetData.findByIdAndDelete(data.id, { session }); + await MongoDatasetData.deleteOne({ _id: data.id }, { session }); + await MongoDatasetDataText.deleteMany({ dataId: data.id }, { session }); await deleteDatasetDataVector({ teamId: data.teamId, idList: data.indexes.map((item) => item.dataId) diff --git a/projects/app/src/service/events/generateQA.ts b/projects/app/src/service/events/generateQA.ts index 755fc3607a99..4a335cc22d49 100644 --- a/projects/app/src/service/events/generateQA.ts +++ b/projects/app/src/service/events/generateQA.ts @@ -17,7 +17,10 @@ import { } from '@fastgpt/service/common/string/tiktoken/index'; import { pushDataListToTrainingQueueByCollectionId } from '@fastgpt/service/core/dataset/training/controller'; import { loadRequestMessages } from '@fastgpt/service/core/chat/utils'; -import { llmCompletionsBodyFormat, llmStreamResponseToText } from '@fastgpt/service/core/ai/utils'; +import { + llmCompletionsBodyFormat, + llmStreamResponseToAnswerText +} from '@fastgpt/service/core/ai/utils'; const reduceQueue = () => { global.qaQueueLen = global.qaQueueLen > 0 ? global.qaQueueLen - 1 : 0; @@ -124,7 +127,7 @@ ${replaceVariable(Prompt_AgentQA.fixedText, { text })}`; modelData ) }); - const answer = await llmStreamResponseToText(chatResponse); + const answer = await llmStreamResponseToAnswerText(chatResponse); const qaArr = formatSplitText(answer, text); // 格式化后的QA对 @@ -139,7 +142,7 @@ ${replaceVariable(Prompt_AgentQA.fixedText, { text })}`; teamId: data.teamId, tmbId: data.tmbId, collectionId: data.collectionId, - trainingMode: TrainingModeEnum.chunk, + mode: TrainingModeEnum.chunk, data: qaArr.map((item) => ({ ...item, chunkIndex: data.chunkIndex @@ -176,9 +179,7 @@ ${replaceVariable(Prompt_AgentQA.fixedText, { text })}`; } } -/** - * 检查文本是否按格式返回 - */ +// Format qa answer function formatSplitText(text: string, rawText: string) { text = text.replace(/\\n/g, '\n'); // 将换行符替换为空格 const regex = /Q\d+:(\s*)(.*)(\s*)A\d+:(\s*)([\s\S]*?)(?=Q\d|$)/g; // 匹配Q和A的正则表达式 @@ -191,13 +192,7 @@ function formatSplitText(text: string, rawText: string) { if (q) { result.push({ q, - a, - indexes: [ - { - defaultIndex: true, - text: `${q}\n${a.trim().replace(/\n\s*/g, '\n')}` - } - ] + a }); } } @@ -208,13 +203,7 @@ function formatSplitText(text: string, rawText: string) { chunks.forEach((chunk) => { result.push({ q: chunk, - a: '', - indexes: [ - { - defaultIndex: true, - text: chunk - } - ] + a: '' }); }); } diff --git a/projects/app/src/service/events/generateVector.ts b/projects/app/src/service/events/generateVector.ts index 22f35d6b9d2d..a8e4f5ca9f50 100644 --- a/projects/app/src/service/events/generateVector.ts +++ b/projects/app/src/service/events/generateVector.ts @@ -20,6 +20,16 @@ const reduceQueue = () => { return global.vectorQueueLen === 0; }; +const reduceQueueAndReturn = (delay = 0) => { + reduceQueue(); + if (delay) { + setTimeout(() => { + generateVector(); + }, delay); + } else { + generateVector(); + } +}; /* 索引生成队列。每导入一次,就是一个单独的线程 */ export async function generateVector(): Promise<any> { @@ -45,20 +55,7 @@ export async function generateVector(): Promise<any> { lockTime: new Date(), $inc: { retryCount: -1 } } - ).select({ - _id: 1, - teamId: 1, - tmbId: 1, - datasetId: 1, - collectionId: 1, - q: 1, - a: 1, - chunkIndex: 1, - dataId: 1, - indexes: 1, - model: 1, - billId: 1 - }); + ); // task preemption if (!data) { @@ -85,14 +82,12 @@ export async function generateVector(): Promise<any> { } if (error) { addLog.error(`[Vector Queue] Error`, { error }); - reduceQueue(); - return generateVector(); + return reduceQueueAndReturn(); } // auth balance if (!(await checkTeamAiPointsAndLock(data.teamId))) { - reduceQueue(); - return generateVector(); + return reduceQueueAndReturn(); } addLog.info(`[Vector Queue] Start`); @@ -119,15 +114,10 @@ export async function generateVector(): Promise<any> { time: Date.now() - start }); - reduceQueue(); - generateVector(); + return reduceQueueAndReturn(); } catch (err: any) { addLog.error(`[Vector Queue] Error`, err); - reduceQueue(); - - setTimeout(() => { - generateVector(); - }, 1000); + return reduceQueueAndReturn(1000); } } @@ -192,7 +182,7 @@ const rebuildData = async ({ retryCount: 50 } ], - { session } + { session, ordered: true } ); } }); diff --git a/projects/app/src/service/mongo.ts b/projects/app/src/service/mongo.ts index 3d043cdb14d8..eb1b5bd33948 100644 --- a/projects/app/src/service/mongo.ts +++ b/projects/app/src/service/mongo.ts @@ -37,7 +37,7 @@ export async function initRootUser(retry = 3): Promise<any> { password: hashStr(psw) } ], - { session } + { session, ordered: true } ); rootId = _id; } diff --git a/projects/app/src/service/support/wallet/usage/push.ts b/projects/app/src/service/support/wallet/usage/push.ts index fe14f70586ac..c6580ca91880 100644 --- a/projects/app/src/service/support/wallet/usage/push.ts +++ b/projects/app/src/service/support/wallet/usage/push.ts @@ -127,12 +127,12 @@ export const pushGenerateVectorUsage = ({ createUsage({ teamId, tmbId, - appName: i18nT('common:support.wallet.moduleName.index'), + appName: i18nT('account_usage:embedding_index'), totalPoints, source, list: [ { - moduleName: i18nT('common:support.wallet.moduleName.index'), + moduleName: i18nT('account_usage:embedding_index'), amount: totalVector, model: vectorModelName, inputTokens @@ -203,7 +203,7 @@ export const pushQuestionGuideUsage = ({ }); }; -export function pushAudioSpeechUsage({ +export const pushAudioSpeechUsage = ({ appName = i18nT('common:support.wallet.usage.Audio Speech'), model, charsLength, @@ -217,7 +217,7 @@ export function pushAudioSpeechUsage({ teamId: string; tmbId: string; source: UsageSourceEnum; -}) { +}) => { const { totalPoints, modelName } = formatModelChars2Points({ model, inputTokens: charsLength, @@ -239,9 +239,9 @@ export function pushAudioSpeechUsage({ } ] }); -} +}; -export function pushWhisperUsage({ +export const pushWhisperUsage = ({ teamId, tmbId, duration @@ -249,7 +249,7 @@ export function pushWhisperUsage({ teamId: string; tmbId: string; duration: number; -}) { +}) => { const whisperModel = getDefaultTTSModel(); if (!whisperModel) return; @@ -278,4 +278,4 @@ export function pushWhisperUsage({ } ] }); -} +}; diff --git a/projects/app/src/test/utils.ts b/projects/app/src/test/utils.ts deleted file mode 100644 index 933cf7487ba3..000000000000 --- a/projects/app/src/test/utils.ts +++ /dev/null @@ -1,94 +0,0 @@ -import { ERROR_ENUM } from '@fastgpt/global/common/error/errorCode'; - -export type TestTokenType = { - userId: string; - teamId: string; - tmbId: string; - isRoot: boolean; -}; - -export type TestRequest = { - headers: { - cookie?: { - token?: TestTokenType; - }; - authorization?: string; // testkey - rootkey?: string; // rootkey - }; - query: { - [key: string]: string; - }; - body: { - [key: string]: string; - }; -}; - -export function getTestRequest<Q = any, B = any>({ - query = {}, - body = {}, - authToken = true, - // authRoot = false, - // authApiKey = false, - user -}: { - query?: Partial<Q>; - body?: Partial<B>; - authToken?: boolean; - authRoot?: boolean; - authApiKey?: boolean; - user?: { - uid: string; - tmbId: string; - teamId: string; - isRoot: boolean; - }; -}): [any, any] { - const headers: TestRequest['headers'] = {}; - if (authToken) { - headers.cookie = { - token: { - userId: String(user?.uid || ''), - teamId: String(user?.teamId || ''), - tmbId: String(user?.tmbId || ''), - isRoot: user?.isRoot || false - } - }; - } - return [ - { - headers, - query, - body - }, - {} - ]; -} - -export const parseHeaderCertMock = async ({ - req, - authToken = true, - authRoot = false, - authApiKey = false -}: { - req: TestRequest; - authToken?: boolean; - authRoot?: boolean; - authApiKey?: boolean; -}): Promise<TestTokenType> => { - if (authToken) { - const token = req.headers?.cookie?.token; - if (!token) { - return Promise.reject(ERROR_ENUM.unAuthorization); - } - return token; - } - // if (authRoot) { - // // TODO: unfinished - // return req.headers.rootkey; - // } - // if (authApiKey) { - // // TODO: unfinished - // return req.headers.authorization; - // } - return {} as any; -}; diff --git a/projects/app/src/test/workflow/loopTest.json b/projects/app/src/test/workflow/loopTest.json new file mode 100644 index 000000000000..6c1735652cef --- /dev/null +++ b/projects/app/src/test/workflow/loopTest.json @@ -0,0 +1,358 @@ +{ + "nodes": [ + { + "nodeId": "userGuide", + "name": "common:core.module.template.system_config", + "intro": "common:core.module.template.system_config_info", + "avatar": "core/workflow/template/systemConfig", + "flowNodeType": "userGuide", + "position": { + "x": 220.4077028616387, + "y": -429.3158723159836 + }, + "version": "481", + "inputs": [ + { + "key": "welcomeText", + "renderTypeList": [ + "hidden" + ], + "valueType": "string", + "label": "core.app.Welcome Text", + "value": "" + }, + { + "key": "variables", + "renderTypeList": [ + "hidden" + ], + "valueType": "any", + "label": "core.app.Chat Variable", + "value": [] + }, + { + "key": "questionGuide", + "valueType": "any", + "renderTypeList": [ + "hidden" + ], + "label": "core.app.Question Guide", + "value": { + "open": false + } + }, + { + "key": "tts", + "renderTypeList": [ + "hidden" + ], + "valueType": "any", + "label": "", + "value": { + "type": "web" + } + }, + { + "key": "whisper", + "renderTypeList": [ + "hidden" + ], + "valueType": "any", + "label": "", + "value": { + "open": false, + "autoSend": false, + "autoTTSResponse": false + } + }, + { + "key": "scheduleTrigger", + "renderTypeList": [ + "hidden" + ], + "valueType": "any", + "label": "", + "value": null + } + ], + "outputs": [] + }, + { + "nodeId": "448745", + "name": "common:core.module.template.work_start", + "intro": "", + "avatar": "core/workflow/template/workflowStart", + "flowNodeType": "workflowStart", + "position": { + "x": 773.4174945178407, + "y": -331.3158723159836 + }, + "version": "481", + "inputs": [ + { + "key": "userChatInput", + "renderTypeList": [ + "reference", + "textarea" + ], + "valueType": "string", + "label": "common:core.module.input.label.user question", + "required": true, + "toolDescription": "用户问题", + "debugLabel": "" + } + ], + "outputs": [ + { + "id": "userChatInput", + "key": "userChatInput", + "label": "common:core.module.input.label.user question", + "type": "static", + "valueType": "string", + "description": "" + } + ] + }, + { + "nodeId": "nlv8iMRsvgkA", + "name": "批量执行", + "intro": "输入一个数组,遍历数组并将每一个数组元素作为输入元素,执行工作流。", + "avatar": "core/workflow/template/loop", + "flowNodeType": "loop", + "showStatus": true, + "position": { + "x": 1236, + "y": -593 + }, + "version": "4811", + "inputs": [ + { + "key": "loopInputArray", + "renderTypeList": [ + "reference" + ], + "valueType": "arrayNumber", + "required": true, + "label": "数组", + "value": [ + [ + "VARIABLE_NODE_ID", + "list" + ] + ], + "valueDesc": "", + "description": "", + "debugLabel": "", + "toolDescription": "" + }, + { + "key": "childrenNodeIdList", + "renderTypeList": [ + "hidden" + ], + "valueType": "arrayString", + "label": "", + "value": [ + "tRxC7faEoGuE", + "cGnptXbKAyMN" + ] + }, + { + "key": "nodeWidth", + "renderTypeList": [ + "hidden" + ], + "valueType": "number", + "label": "", + "value": 1246.6404923618281 + }, + { + "key": "nodeHeight", + "renderTypeList": [ + "hidden" + ], + "valueType": "number", + "label": "", + "value": 642.1566957382456 + }, + { + "key": "loopNodeInputHeight", + "renderTypeList": [ + "hidden" + ], + "valueType": "number", + "label": "", + "value": 83, + "valueDesc": "", + "description": "", + "debugLabel": "", + "toolDescription": "" + } + ], + "outputs": [ + { + "id": "loopArray", + "key": "loopArray", + "label": "数组执行结果", + "type": "static", + "valueType": "arrayAny", + "valueDesc": "", + "description": "" + } + ] + }, + { + "nodeId": "tRxC7faEoGuE", + "parentNodeId": "nlv8iMRsvgkA", + "name": "开始", + "avatar": "core/workflow/template/loopStart", + "flowNodeType": "loopStart", + "showStatus": false, + "position": { + "x": 1305.782937883576, + "y": -270.30845154767246 + }, + "version": "4811", + "inputs": [ + { + "key": "loopStartInput", + "renderTypeList": [ + "hidden" + ], + "valueType": "any", + "label": "", + "required": true, + "value": "" + }, + { + "key": "loopStartIndex", + "renderTypeList": [ + "hidden" + ], + "valueType": "number", + "label": "workflow:Array_element_index" + } + ], + "outputs": [ + { + "id": "loopStartIndex", + "key": "loopStartIndex", + "label": "workflow:Array_element_index", + "type": "static", + "valueType": "number" + }, + { + "id": "loopStartInput", + "key": "loopStartInput", + "label": "数组元素", + "type": "static", + "valueType": "number" + } + ] + }, + { + "nodeId": "cGnptXbKAyMN", + "parentNodeId": "nlv8iMRsvgkA", + "name": "结束", + "avatar": "core/workflow/template/loopEnd", + "flowNodeType": "loopEnd", + "showStatus": false, + "position": { + "x": 1929.4234302454042, + "y": 135.8482441905731 + }, + "version": "4811", + "inputs": [ + { + "key": "loopEndInput", + "renderTypeList": [ + "reference" + ], + "valueType": "any", + "label": "", + "required": true, + "value": [] + } + ], + "outputs": [] + }, + { + "nodeId": "zpOBWBxfyUap", + "parentNodeId": "nlv8iMRsvgkA", + "name": "指定回复", + "intro": "该模块可以直接回复一段指定的内容。常用于引导、提示。非字符串内容传入时,会转成字符串进行输出。", + "avatar": "core/workflow/template/reply", + "flowNodeType": "answerNode", + "position": { + "x": 1806.423430245404, + "y": -217.4185397094268 + }, + "version": "481", + "inputs": [ + { + "key": "text", + "renderTypeList": [ + "textarea", + "reference" + ], + "valueType": "any", + "required": true, + "label": "回复的内容", + "description": "可以使用 \\n 来实现连续换行。\n可以通过外部模块输入实现回复,外部模块输入时会覆盖当前填写的内容。\n如传入非字符串类型数据将会自动转成字符串", + "placeholder": "common:core.module.input.description.Response content", + "value": "{{$tRxC7faEoGuE.loopStartInput$}}", + "valueDesc": "", + "debugLabel": "", + "toolDescription": "" + } + ], + "outputs": [] + } + ], + "edges": [ + { + "source": "448745", + "target": "nlv8iMRsvgkA", + "sourceHandle": "448745-source-right", + "targetHandle": "nlv8iMRsvgkA-target-left" + }, + { + "source": "tRxC7faEoGuE", + "target": "zpOBWBxfyUap", + "sourceHandle": "tRxC7faEoGuE-source-right", + "targetHandle": "zpOBWBxfyUap-target-left" + } + ], + "chatConfig": { + "variables": [ + { + "id": "04sm7m", + "key": "list", + "label": "list", + "type": "custom", + "description": "", + "required": false, + "valueType": "arrayNumber", + "list": [ + { + "value": "", + "label": "" + } + ], + "defaultValue": "[1,2,3]", + "enums": [ + { + "value": "", + "label": "" + } + ] + } + ], + "_id": "67a8d281b54c01f7bd95c995", + "scheduledTriggerConfig": { + "cronString": "", + "timezone": "Asia/Shanghai", + "defaultPrompt": "" + } + } +} diff --git a/projects/app/src/test/workflow/simple.json b/projects/app/src/test/workflow/simple.json new file mode 100644 index 000000000000..ce6c0a33c318 --- /dev/null +++ b/projects/app/src/test/workflow/simple.json @@ -0,0 +1,319 @@ +{ + "nodes": [ + { + "nodeId": "userGuide", + "name": "系统配置", + "intro": "", + "avatar": "core/workflow/template/systemConfig", + "flowNodeType": "userGuide", + "position": { + "x": 531.2422736065552, + "y": -486.7611729549753 + }, + "version": "481", + "inputs": [], + "outputs": [] + }, + { + "nodeId": "workflowStartNodeId", + "name": "流程开始", + "intro": "", + "avatar": "core/workflow/template/workflowStart", + "flowNodeType": "workflowStart", + "position": { + "x": 531.2422736065552, + "y": 244.69591764653183 + }, + "version": "481", + "inputs": [ + { + "key": "userChatInput", + "renderTypeList": [ + "reference", + "textarea" + ], + "valueType": "string", + "label": "workflow:user_question", + "toolDescription": "用户问题", + "required": true, + "debugLabel": "" + } + ], + "outputs": [ + { + "id": "userChatInput", + "key": "userChatInput", + "label": "common:core.module.input.label.user question", + "type": "static", + "valueType": "string", + "description": "" + }, + { + "id": "userFiles", + "key": "userFiles", + "label": "app:workflow.user_file_input", + "description": "app:workflow.user_file_input_desc", + "type": "static", + "valueType": "arrayString" + } + ] + }, + { + "nodeId": "7BdojPlukIQw", + "name": "AI 对话", + "intro": "AI 大模型对话", + "avatar": "core/workflow/template/aiChat", + "flowNodeType": "chatNode", + "showStatus": true, + "position": { + "x": 1106.3238387960757, + "y": -350.6030674683474 + }, + "version": "4813", + "inputs": [ + { + "key": "model", + "renderTypeList": [ + "settingLLMModel", + "reference" + ], + "label": "", + "valueType": "string", + "value": "deepseek-reasoner", + "debugLabel": "", + "toolDescription": "" + }, + { + "key": "temperature", + "renderTypeList": [ + "hidden" + ], + "label": "", + "value": 0, + "valueType": "number", + "min": 0, + "max": 10, + "step": 1, + "debugLabel": "", + "toolDescription": "" + }, + { + "key": "maxToken", + "renderTypeList": [ + "hidden" + ], + "label": "", + "value": 8000, + "valueType": "number", + "min": 100, + "max": 4000, + "step": 50, + "debugLabel": "", + "toolDescription": "" + }, + { + "key": "isResponseAnswerText", + "renderTypeList": [ + "hidden" + ], + "label": "", + "value": true, + "valueType": "boolean", + "debugLabel": "", + "toolDescription": "" + }, + { + "key": "aiChatQuoteRole", + "renderTypeList": [ + "hidden" + ], + "label": "", + "valueType": "string", + "value": "system", + "debugLabel": "", + "toolDescription": "" + }, + { + "key": "quoteTemplate", + "renderTypeList": [ + "hidden" + ], + "label": "", + "valueType": "string", + "debugLabel": "", + "toolDescription": "" + }, + { + "key": "quotePrompt", + "renderTypeList": [ + "hidden" + ], + "label": "", + "valueType": "string", + "debugLabel": "", + "toolDescription": "" + }, + { + "key": "aiChatVision", + "renderTypeList": [ + "hidden" + ], + "label": "", + "valueType": "boolean", + "value": true, + "debugLabel": "", + "toolDescription": "" + }, + { + "key": "aiChatReasoning", + "renderTypeList": [ + "hidden" + ], + "label": "", + "valueType": "boolean", + "value": true, + "debugLabel": "", + "toolDescription": "" + }, + { + "key": "systemPrompt", + "renderTypeList": [ + "textarea", + "reference" + ], + "max": 3000, + "valueType": "string", + "label": "core.ai.Prompt", + "description": "core.app.tip.systemPromptTip", + "placeholder": "core.app.tip.chatNodeSystemPromptTip", + "value": "", + "debugLabel": "", + "toolDescription": "" + }, + { + "key": "history", + "renderTypeList": [ + "numberInput", + "reference" + ], + "valueType": "chatHistory", + "label": "core.module.input.label.chat history", + "required": true, + "min": 0, + "max": 50, + "value": 6, + "description": "workflow:max_dialog_rounds", + "debugLabel": "", + "toolDescription": "" + }, + { + "key": "quoteQA", + "renderTypeList": [ + "settingDatasetQuotePrompt" + ], + "label": "", + "debugLabel": "知识库引用", + "description": "", + "valueType": "datasetQuote", + "toolDescription": "" + }, + { + "key": "fileUrlList", + "renderTypeList": [ + "reference", + "input" + ], + "label": "app:file_quote_link", + "debugLabel": "文件链接", + "valueType": "arrayString", + "value": [ + [ + "workflowStartNodeId", + "userFiles" + ] + ], + "toolDescription": "" + }, + { + "key": "userChatInput", + "renderTypeList": [ + "reference", + "textarea" + ], + "valueType": "string", + "label": "common:core.module.input.label.user question", + "required": true, + "toolDescription": "用户问题", + "value": [ + "workflowStartNodeId", + "userChatInput" + ], + "debugLabel": "" + } + ], + "outputs": [ + { + "id": "history", + "key": "history", + "required": true, + "label": "common:core.module.output.label.New context", + "description": "将本次回复内容拼接上历史记录,作为新的上下文返回", + "valueType": "chatHistory", + "valueDesc": "{\n obj: System | Human | AI;\n value: string;\n}[]", + "type": "static" + }, + { + "id": "answerText", + "key": "answerText", + "required": true, + "label": "common:core.module.output.label.Ai response content", + "description": "将在 stream 回复完毕后触发", + "valueType": "string", + "type": "static" + } + ] + } + ], + "edges": [ + { + "source": "workflowStartNodeId", + "target": "7BdojPlukIQw", + "sourceHandle": "workflowStartNodeId-source-right", + "targetHandle": "7BdojPlukIQw-target-left" + } + ], + "chatConfig": { + "questionGuide": false, + "ttsConfig": { + "type": "web" + }, + "whisperConfig": { + "open": false, + "autoSend": false, + "autoTTSResponse": false + }, + "scheduledTriggerConfig": { + "cronString": "", + "timezone": "Asia/Shanghai", + "defaultPrompt": "" + }, + "chatInputGuide": { + "open": false, + "textList": [], + "customUrl": "" + }, + "instruction": "", + "autoExecute": { + "open": false, + "defaultPrompt": "" + }, + "welcomeText": "", + "variables": [], + "fileSelectConfig": { + "canSelectFile": false, + "canSelectImg": false, + "maxFiles": 10 + }, + "_id": "66f4c2f5e9e4e93a95141004" + } +} diff --git a/projects/app/src/test/workflow/workflow.test.ts b/projects/app/src/test/workflow/workflow.test.ts new file mode 100644 index 000000000000..126656e97c2a --- /dev/null +++ b/projects/app/src/test/workflow/workflow.test.ts @@ -0,0 +1,82 @@ +import { readFileSync } from 'fs'; +import { resolve } from 'path'; +import { describe, it, expect, beforeEach, vi } from 'vitest'; +import { dispatchWorkFlow } from '@fastgpt/service/core/workflow/dispatch'; +import { + getWorkflowEntryNodeIds, + storeNodes2RuntimeNodes +} from '@fastgpt/global/core/workflow/runtime/utils'; +import { ChatItemValueTypeEnum } from '@fastgpt/global/core/chat/constants'; +vi.mock(import('@fastgpt/service/common/string/tiktoken'), async (importOriginal) => { + const mod = await importOriginal(); + return { + ...mod, + countGptMessagesTokens: async () => { + return 1; + } + }; +}); + +vi.mock(import('@fastgpt/service/support/wallet/usage/utils'), async (importOriginal) => { + const mod = await importOriginal(); + return { + ...mod, + formatModelChars2Points: () => ({ + modelName: 'test', + totalPoints: 1 + }) + }; +}); + +const testWorkflow = async (path: string) => { + const workflowStr = readFileSync(resolve(path), 'utf-8'); + const workflow = JSON.parse(workflowStr); + const { nodes, edges, chatConfig } = workflow; + let runtimeNodes = storeNodes2RuntimeNodes(nodes, getWorkflowEntryNodeIds(nodes)); + const variables = {}; + const { assistantResponses, flowResponses } = await dispatchWorkFlow({ + mode: 'test', + runningAppInfo: { + id: 'test', + teamId: 'test', + tmbId: 'test' + }, + runningUserInfo: { + tmbId: 'test', + teamId: 'test' + }, + timezone: 'Asia/Shanghai', + externalProvider: {}, + uid: 'test', + runtimeNodes, + runtimeEdges: edges, + variables, + query: [ + { + type: ChatItemValueTypeEnum.text, + text: { + content: '你是谁' + } + } + ], + chatConfig, + histories: [], + stream: false, + maxRunTimes: 5 + }); + expect(assistantResponses).toBeDefined(); + expect(assistantResponses[0].text?.content).toBeDefined(); + return { + assistantResponses, + flowResponses + }; +}; + +it('Workflow test: simple workflow', async () => { + // create a simple app + await testWorkflow('projects/app/src/test/workflow/simple.json'); +}); + +it('Workflow test: output test', async () => { + console.log(await testWorkflow('projects/app/src/test/workflow/loopTest.json')); +}); diff --git a/projects/app/src/web/common/file/api.ts b/projects/app/src/web/common/file/api.ts index 32a899699df6..d11ebabd0890 100644 --- a/projects/app/src/web/common/file/api.ts +++ b/projects/app/src/web/common/file/api.ts @@ -1,4 +1,3 @@ -import type { PreviewContextProps } from '@/pages/api/common/file/previewContent'; import { GET, POST } from '@/web/common/api/request'; import type { UploadImgProps } from '@fastgpt/global/common/file/api.d'; import { AxiosProgressEvent } from 'axios'; @@ -19,11 +18,3 @@ export const postUploadFiles = ( 'Content-Type': 'multipart/form-data; charset=utf-8' } }); - -export const getPreviewFileContent = (data: PreviewContextProps) => - POST<{ - previewContent: string; - totalLength: number; - }>('/common/file/previewContent', data, { - timeout: 600000 - }); diff --git a/projects/app/src/web/common/system/useSystemStore.ts b/projects/app/src/web/common/system/useSystemStore.ts index 68feecd3f77b..373b389e4f3a 100644 --- a/projects/app/src/web/common/system/useSystemStore.ts +++ b/projects/app/src/web/common/system/useSystemStore.ts @@ -53,6 +53,7 @@ type State = { defaultModels: SystemDefaultModelType; llmModelList: LLMModelItemType[]; datasetModelList: LLMModelItemType[]; + getVlmModelList: () => LLMModelItemType[]; embeddingModelList: EmbeddingModelItemType[]; ttsModelList: TTSModelType[]; reRankModelList: ReRankModelItemType[]; @@ -134,6 +135,9 @@ export const useSystemStore = create<State>()( ttsModelList: [], reRankModelList: [], sttModelList: [], + getVlmModelList: () => { + return get().llmModelList.filter((item) => item.vision); + }, initStaticData(res) { set((state) => { state.initDataBufferId = res.bufferId; diff --git a/projects/app/src/web/common/utils/voice.ts b/projects/app/src/web/common/utils/voice.ts index d912dba87cec..bbc0cce2f5ce 100644 --- a/projects/app/src/web/common/utils/voice.ts +++ b/projects/app/src/web/common/utils/voice.ts @@ -178,7 +178,7 @@ export const useAudioPlay = ( await new Promise((resolve) => { sourceBuffer.onupdateend = resolve; - sourceBuffer.appendBuffer(value.buffer); + sourceBuffer.appendBuffer(value.buffer as any); }); } } catch (error) { @@ -311,7 +311,7 @@ export const useAudioPlay = ( await new Promise((resolve) => { buffer.onupdateend = resolve; - buffer.appendBuffer(value.buffer); + buffer.appendBuffer(value.buffer as any); }); } } catch (error) { diff --git a/projects/app/src/web/core/ai/channel.ts b/projects/app/src/web/core/ai/channel.ts new file mode 100644 index 000000000000..fd4534a3f9c1 --- /dev/null +++ b/projects/app/src/web/core/ai/channel.ts @@ -0,0 +1,188 @@ +import axios, { Method, AxiosResponse } from 'axios'; +import { getWebReqUrl } from '@fastgpt/web/common/system/utils'; +import { + ChannelInfoType, + ChannelListResponseType, + ChannelLogListItemType, + CreateChannelProps +} from '@/global/aiproxy/type'; +import { ChannelStatusEnum } from '@/global/aiproxy/constants'; + +interface ResponseDataType { + success: boolean; + message: string; + data: any; +} + +/** + * 请求成功,检查请求头 + */ +function responseSuccess(response: AxiosResponse<ResponseDataType>) { + return response; +} +/** + * 响应数据检查 + */ +function checkRes(data: ResponseDataType) { + if (data === undefined) { + console.log('error->', data, 'data is empty'); + return Promise.reject('服务器异常'); + } else if (!data.success) { + return Promise.reject(data); + } + return data.data; +} + +/** + * 响应错误 + */ +function responseError(err: any) { + console.log('error->', '请求错误', err); + const data = err?.response?.data || err; + + if (!err) { + return Promise.reject({ message: '未知错误' }); + } + if (typeof err === 'string') { + return Promise.reject({ message: err }); + } + if (typeof data === 'string') { + return Promise.reject(data); + } + + return Promise.reject(data); +} + +/* 创建请求实例 */ +const instance = axios.create({ + timeout: 60000, // 超时时间 + headers: { + 'content-type': 'application/json' + } +}); + +/* 响应拦截 */ +instance.interceptors.response.use(responseSuccess, (err) => Promise.reject(err)); + +function request(url: string, data: any, method: Method): any { + /* 去空 */ + for (const key in data) { + if (data[key] === undefined) { + delete data[key]; + } + } + + return instance + .request({ + baseURL: getWebReqUrl('/api/aiproxy/api'), + url, + method, + data: ['POST', 'PUT'].includes(method) ? data : undefined, + params: !['POST', 'PUT'].includes(method) ? data : undefined + }) + .then((res) => checkRes(res.data)) + .catch((err) => responseError(err)); +} + +/** + * api请求方式 + * @param {String} url + * @param {Any} params + * @param {Object} config + * @returns + */ +export function GET<T = undefined>(url: string, params = {}): Promise<T> { + return request(url, params, 'GET'); +} +export function POST<T = undefined>(url: string, data = {}): Promise<T> { + return request(url, data, 'POST'); +} +export function PUT<T = undefined>(url: string, data = {}): Promise<T> { + return request(url, data, 'PUT'); +} +export function DELETE<T = undefined>(url: string, data = {}): Promise<T> { + return request(url, data, 'DELETE'); +} + +// ====== API ====== +export const getChannelList = () => + GET<ChannelListResponseType>('/channels/all', { + page: 1, + perPage: 10 + }); + +export const getChannelProviders = () => + GET< + Record< + number, + { + defaultBaseUrl: string; + keyHelp: string; + name: string; + } + > + >('/channels/type_metas'); + +export const postCreateChannel = (data: CreateChannelProps) => + POST(`/createChannel`, { + type: data.type, + name: data.name, + base_url: data.base_url, + models: data.models, + model_mapping: data.model_mapping, + key: data.key, + priority: 1 + }); + +export const putChannelStatus = (id: number, status: ChannelStatusEnum) => + POST(`/channel/${id}/status`, { + status + }); +export const putChannel = (data: ChannelInfoType) => + PUT(`/channel/${data.id}`, { + type: data.type, + name: data.name, + base_url: data.base_url, + models: data.models, + model_mapping: data.model_mapping, + key: data.key, + status: data.status, + priority: data.priority ? Math.max(data.priority, 1) : undefined + }); + +export const deleteChannel = (id: number) => DELETE(`/channel/${id}`); + +export const getChannelLog = (params: { + request_id?: string; + channel?: string; + model_name?: string; + code_type?: 'all' | 'success' | 'error'; + start_timestamp: number; + end_timestamp: number; + offset: number; + pageSize: number; +}) => + GET<{ + logs: ChannelLogListItemType[]; + total: number; + }>(`/logs/search`, { + request_id: params.request_id, + channel: params.channel, + model_name: params.model_name, + code_type: params.code_type, + start_timestamp: params.start_timestamp, + end_timestamp: params.end_timestamp, + p: Math.floor(params.offset / params.pageSize) + 1, + per_page: params.pageSize + }).then((res) => { + return { + list: res.logs, + total: res.total + }; + }); + +export const getLogDetail = (id: number) => + GET<{ + request_body: string; + response_body: string; + }>(`/logs/detail/${id}`); diff --git a/projects/app/src/web/core/app/utils.ts b/projects/app/src/web/core/app/utils.ts index 016a6bcefa54..546efea02df8 100644 --- a/projects/app/src/web/core/app/utils.ts +++ b/projects/app/src/web/core/app/utils.ts @@ -504,6 +504,13 @@ export function form2AppWorkflow( label: '', valueType: WorkflowIOValueTypeEnum.boolean, value: true + }, + { + key: NodeInputKeyEnum.aiChatReasoning, + renderTypeList: [FlowNodeInputTypeEnum.hidden], + label: '', + valueType: WorkflowIOValueTypeEnum.boolean, + value: formData.aiSettings.aiChatReasoning } ], outputs: ToolModule.outputs diff --git a/projects/app/src/web/core/dataset/api.ts b/projects/app/src/web/core/dataset/api.ts index 94a467c46e7e..e44c67f098b4 100644 --- a/projects/app/src/web/core/dataset/api.ts +++ b/projects/app/src/web/core/dataset/api.ts @@ -215,7 +215,10 @@ export const getDatasetTrainingQueue = (datasetId: string) => }); export const getPreviewChunks = (data: PostPreviewFilesChunksProps) => - POST<PreviewChunksResponse>('/core/dataset/file/getPreviewChunks', data); + POST<PreviewChunksResponse>('/core/dataset/file/getPreviewChunks', data, { + maxQuantity: 1, + timeout: 600000 + }); /* ================== read source ======================== */ export const getCollectionSource = (data: readCollectionSourceBody) => diff --git a/projects/app/src/web/core/dataset/constants.ts b/projects/app/src/web/core/dataset/constants.ts index be67d3736bd5..860e735025ad 100644 --- a/projects/app/src/web/core/dataset/constants.ts +++ b/projects/app/src/web/core/dataset/constants.ts @@ -1,8 +1,8 @@ import { defaultQAModels, defaultVectorModels } from '@fastgpt/global/core/ai/model'; import { + DatasetCollectionDataProcessModeEnum, DatasetCollectionTypeEnum, - DatasetTypeEnum, - TrainingModeEnum + DatasetTypeEnum } from '@fastgpt/global/core/dataset/constants'; import type { DatasetCollectionItemType, @@ -25,6 +25,7 @@ export const defaultDatasetDetail: DatasetItemType = { permission: new DatasetPermission(), vectorModel: defaultVectorModels[0], agentModel: defaultQAModels[0], + vlmModel: defaultQAModels[0], inheritPermission: true }; @@ -57,13 +58,13 @@ export const defaultCollectionDetail: DatasetCollectionItemType = { sourceName: '', sourceId: '', createTime: new Date(), - trainingType: TrainingModeEnum.chunk, + trainingType: DatasetCollectionDataProcessModeEnum.chunk, chunkSize: 0, permission: new DatasetPermission(), indexAmount: 0 }; -export enum ImportProcessWayEnum { +export enum ChunkSettingModeEnum { auto = 'auto', custom = 'custom' } diff --git a/projects/app/src/web/core/dataset/context/datasetPageContext.tsx b/projects/app/src/web/core/dataset/context/datasetPageContext.tsx index 46ab863617ab..26601605fbbc 100644 --- a/projects/app/src/web/core/dataset/context/datasetPageContext.tsx +++ b/projects/app/src/web/core/dataset/context/datasetPageContext.tsx @@ -18,6 +18,7 @@ import { DatasetItemType, DatasetTagType } from '@fastgpt/global/core/dataset/ty import { useSystemStore } from '@/web/common/system/useSystemStore'; import { ParentTreePathItemType } from '@fastgpt/global/common/parentFolder/type'; import { useRequest2 } from '@fastgpt/web/hooks/useRequest'; +import { getWebLLMModel } from '@/web/common/system/utils'; type DatasetPageContextType = { datasetId: string; @@ -116,6 +117,8 @@ export const DatasetPageContextProvider = ({ setDatasetDetail((state) => ({ ...state, ...data, + agentModel: getWebLLMModel(data.agentModel), + vlmModel: getWebLLMModel(data.vlmModel), apiServer: data.apiServer ? { baseUrl: data.apiServer.baseUrl, diff --git a/projects/app/src/web/core/dataset/type.d.ts b/projects/app/src/web/core/dataset/type.d.ts index 6470e783b26c..a095bc7981e1 100644 --- a/projects/app/src/web/core/dataset/type.d.ts +++ b/projects/app/src/web/core/dataset/type.d.ts @@ -1,6 +1,6 @@ import type { PushDatasetDataChunkProps } from '@fastgpt/global/core/dataset/api'; import { TrainingModeEnum } from '@fastgpt/global/core/dataset/constants'; -import { ImportProcessWayEnum } from './constants'; +import { ChunkSettingModeEnum } from './constants'; import { UseFormReturn } from 'react-hook-form'; import { APIFileItem } from '@fastgpt/global/core/dataset/apiDataset'; @@ -44,7 +44,7 @@ export type ImportSourceParamsType = UseFormReturn< customSplitChar: string; prompt: string; mode: TrainingModeEnum; - way: ImportProcessWayEnum; + way: ChunkSettingModeEnum; }, any >; diff --git a/projects/app/tsconfig.json b/projects/app/tsconfig.json index 7d58a40e28bb..9782f283739c 100644 --- a/projects/app/tsconfig.json +++ b/projects/app/tsconfig.json @@ -3,8 +3,10 @@ "compilerOptions": { "baseUrl": ".", "paths": { - "@/*": ["./src/*"] + "@/*": ["./src/*"], + "@test/*": ["../../test/*"] } }, - "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", "**/*.d.ts", "../../packages/**/*.d.ts"] + "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", "**/*.d.ts", "../../packages/**/*.d.ts"], + "exclude": ["**/*.test.ts"] } diff --git a/python/README.md b/python/README.md deleted file mode 100644 index d4ceed52c327..000000000000 --- a/python/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# 目录说明 - -该目录为 python 辅助代码,非主项目代码,仅供学习使用,未参与实际生产。 diff --git a/python/api/README.md b/python/api/README.md deleted file mode 100644 index 0c1e0434eea6..000000000000 --- a/python/api/README.md +++ /dev/null @@ -1,54 +0,0 @@ -# FastGPT-python-API -作者:stakeswky。有问题请这样联系我:stakeswky@gmail.com -## 1. 项目简介 -该API以python为技术栈,为fastgpt提供了一个简单易用的接口,方便fastgpt处理各种任务。该API的主要功能包括: -1. Word & PDF 图文提取 -在现有的文件读取中,fastgpt只能读取文件中的文字,而无法读取图片。该API可以将word和pdf中的文字和图片提取出来,方便fastgpt进行处理。 - -2. 网页递归获取 -该API可以递归获取指定页面的内容和挖掘该页面存在的链接指向页面的内容,请注意,该功能现在仅支持获取静态页面的内容,如果出现动态页面,可能会出现无法获取的情况。 - -3. (研发中。。) - -## 2. 安装方法 -### 必要的知识 -会使用Google -python的基本用法 -docker的基本用法 -百度OCR-API的文档:https://ai.baidu.com/ai-doc/OCR/Ek3h7xypm - -### 2.1 源码安装 -该API依赖于python3.8,请确保您的python版本符合要求。 -```shell -pip install -r requirements.txt -``` -引入环境变量:APP_ID,API_KEY,SECRET_KEY - -然后运行: -```shell -python main.py -``` -启动! - -### 2.2 Docker安装 -一把梭拉现成的镜像,直接拉下来用就行了。 -```shell -docker pull registry.cn-hangzhou.aliyuncs.com/fastgpt_docker/fastgpt_python_api:1.0 -``` -然后运行,三个环境变量记得配置成自己的: -```shell -docker run -d -p 6010:6010 -e APP_ID=<your_app_id> -e API_KEY=<your_api_key> -e SECRET_KEY=<your_secret_key> registry.cn-hangzhou.aliyuncs.com/fastgpt_docker/fastgpt_python_api:1.0 -``` - -或者你也可以自己打镜像 -```shell -docker build -t fastgpt-python-api . -``` -然后运行: -```shell -docker run -d -p 6010:6010 -e APP_ID=<your_app_id> -e API_KEY=<your_api_key> -e SECRET_KEY=<your_secret_key> fastgpt-python-api -``` -## 3. 使用方法 -目录下附带了两个测试案例,分别是word和pdf的图文提取,和网页递归获取。按照那个来使用就好 - - diff --git a/python/api/api.py b/python/api/api.py deleted file mode 100644 index f9aad4eca6ae..000000000000 --- a/python/api/api.py +++ /dev/null @@ -1,74 +0,0 @@ -import os -from fastapi import FastAPI, File, UploadFile -from fastapi.responses import JSONResponse -from pydantic import BaseModel -from services.office2txt import office_to_txt -from typing import List -from fastapi import HTTPException -from services.fetch import get_summary -import aiofiles -import queue -import uuid - - -# 请求模型 -class SummaryRequest(BaseModel): - url: str - level: int - -# 响应模型 -class SummaryResponse(BaseModel): - url: str - title: str - summary: str - -class ExtractedText(BaseModel): - text: str - - -# 文件转文本 -async def process_file(file: UploadFile): - file_ext = os.path.splitext(file.filename)[1].lower() - if file_ext not in ['.docx', '.pdf', '.doc', '.txt']: - return JSONResponse(content={"error": "Unsupported file format"}, status_code=400) - - # 生成唯一的文件名 - unique_filename = f"{uuid.uuid4()}{file_ext}" - - try: - # 读取文件内容并保存到唯一命名的文件中 - async with aiofiles.open(unique_filename, "wb") as out_file: - while True: - contents = await file.read(1024) # 以块的方式读取文件 - if not contents: - break - await out_file.write(contents) - - # 文件处理逻辑,注意传入新的唯一文件名 - extracted_text = office_to_txt(unique_filename) - print(extracted_text) - return {"text": extracted_text} - except Exception as e: - return JSONResponse(content={"error": str(e)}, status_code=500) - finally: - # 清理:删除临时保存的唯一命名文件 - if os.path.exists(unique_filename): - os.remove(unique_filename) - - - -# 定义一个处理网页摘要的函数 -async def process_summary(request): - if request.level < 0: - raise HTTPException(status_code=400, detail="Level must be non-negative.") - try: - # 使用定义的函数来获取网页摘要 - summaries = get_summary(request.url, request.level) - # 将结果转换为响应模型列表 - print(summaries) - return [SummaryResponse(url=url, title=title, summary=summary) for url, title, summary in summaries] - except Exception as e: - raise HTTPException(status_code=500, detail=str(e)) - - - diff --git a/python/api/dockerfile b/python/api/dockerfile deleted file mode 100644 index 8d6fa54fb97f..000000000000 --- a/python/api/dockerfile +++ /dev/null @@ -1,20 +0,0 @@ -# 使用官方Python运行时作为父镜像 -FROM python:3.8 - -# 设置工作目录 -WORKDIR /app - -# 将当前目录内容复制到容器的/app中 -ADD . /app - -RUN pip install --upgrade -i https://pypi.tuna.tsinghua.edu.cn/simple pip -# 安装程序需要的包 -RUN pip install -i https://pypi.tuna.tsinghua.edu.cn/simple --no-cache-dir -r requirements.txt - -RUN python -m nltk.downloader punkt - -# 运行时监听的端口 -EXPOSE 6010 - -# 运行app.py时的命令及其参数 -CMD ["python", "main.py"] diff --git a/python/api/main.py b/python/api/main.py deleted file mode 100644 index bd6133431841..000000000000 --- a/python/api/main.py +++ /dev/null @@ -1,48 +0,0 @@ -from fastapi.middleware.cors import CORSMiddleware -from fastapi import FastAPI, File, UploadFile -import queue -from typing import List -from api import SummaryRequest, SummaryResponse, ExtractedText,process_file,process_summary -import uvicorn - -app = FastAPI() - -app.add_middleware( - CORSMiddleware, - allow_origins=["*"], - allow_credentials=True, - allow_methods=["*"], - allow_headers=["*"], -) - - -q = queue.Queue() - -# 定义一个接口,接收文件并将其放入队列中 -@app.post("/extract_text/", response_model=ExtractedText) -async def extract_text(file: UploadFile = File(...)): - # 将文件对象放入队列中,先进先出 - q.put(file) - # 从队列中取出文件对象,并调用处理函数 - file = q.get() - result = await process_file(file) - # 标记队列中的任务已完成 - q.task_done() - # 返回处理结果 - return result - -# 定义一个接口,接收请求并将其放入队列中 -@app.post("/generate_summary/", response_model=List[SummaryResponse]) -async def generate_summary(request: SummaryRequest): - # 将请求对象放入队列中,先进先出 - q.put(request) - # 从队列中取出请求对象,并调用处理函数 - request = q.get() - result = await process_summary(request) - # 标记队列中的任务已完成 - q.task_done() - # 返回处理结果 - return result - -if __name__ == "__main__": - uvicorn.run(app, host="0.0.0.0", port=6010) \ No newline at end of file diff --git a/python/api/requirements.txt b/python/api/requirements.txt deleted file mode 100644 index 0b7a34f06751..000000000000 --- a/python/api/requirements.txt +++ /dev/null @@ -1,14 +0,0 @@ -aiofiles==23.2.1 -baidu_aip==4.16.12 -beautifulsoup4==4.11.1 -fastapi==0.104.1 -nltk==3.8.1 -pdf2image==1.16.3 -pydantic==1.10.7 -PyPDF2==3.0.1 -python_docx==0.8.11 -python_pptx==0.6.21 -Requests==2.31.0 -uvicorn==0.24.0 -chardet==5.2.0 -python-multipart==0.0.6 \ No newline at end of file diff --git a/python/api/services/fetch.py b/python/api/services/fetch.py deleted file mode 100644 index 92df68d77574..000000000000 --- a/python/api/services/fetch.py +++ /dev/null @@ -1,90 +0,0 @@ -import requests -import bs4 -import nltk -from urllib.parse import urljoin -from time import sleep -import time -import math - -# 全局变量来记录开始时间 -start_time = time.time() - -# 你可以设定一个最大运行时长,比如60秒 -max_run_time = 20 - -# 添加一个简单的IDF计算器 -class SimpleIDFCalculator: - def __init__(self): - self.doc_freq = {} - self.num_docs = 0 - - def add_document(self, doc): - self.num_docs += 1 - words = set(nltk.word_tokenize(doc)) - for word in words: - if word in self.doc_freq: - self.doc_freq[word] += 1 - else: - self.doc_freq[word] = 1 - - def idf(self, word): - return math.log(self.num_docs / (1 + self.doc_freq.get(word, 0))) - - - -# 定义一个函数,用于获取网页的内容,并进行总结 -def get_summary(url, level): - result = [] - visited = set() - idf_calculator = SimpleIDFCalculator() - helper(url, level, result, visited, idf_calculator) - return result - -# 辅助函数 -def helper(url, level, result, visited, idf_calculator): - # # 检查是否超出运行时间限制 - # if time.time() - start_time > max_run_time: - # print("Reached max run time, exiting...") - # return - - if level == 0 or url in visited or not url.startswith("http"): - return - - visited.add(url) - try: - response = requests.get(url) - if response.status_code != 200: - return - soup = bs4.BeautifulSoup(response.text, "html.parser") - title = soup.title.string if soup.title else 'No Title' - text = soup.get_text().strip() - idf_calculator.add_document(text) - sentences = nltk.sent_tokenize(text) - words = nltk.word_tokenize(text) - - scores = {} - for sentence in sentences: - for word in nltk.word_tokenize(sentence): - tf = words.count(word) / len(words) - idf = idf_calculator.idf(word) - scores[sentence] = scores.get(sentence, 0) + (tf * idf) - - summary = " ".join(sorted(scores, key=scores.get, reverse=True)[:10]) - result.append((url, title, summary)) - - sleep(1) # Simple delay to prevent aggressive crawling - - links = soup.find_all("a") - for link in links: - href = link.get("href") - if href: - # Handle relative links - next_url = urljoin(url, href) - helper(next_url, level - 1, result, visited, idf_calculator) - - except Exception as e: - print(f"Error processing {url}: {e}") - -# # 主程序部分,仅作为函数调用示例: -# summary = get_summary('https://zhihu.com', 2) -# print(summary) diff --git a/python/api/services/office2txt.py b/python/api/services/office2txt.py deleted file mode 100644 index 5d9b0ae7f4ff..000000000000 --- a/python/api/services/office2txt.py +++ /dev/null @@ -1,93 +0,0 @@ -import os -import docx -from aip import AipOcr -from io import BytesIO -from PyPDF2 import PdfReader -from pdf2image import convert_from_path - - -# 百度OCR API设置 -APP_ID = os.environ.get('APP_ID','xxx') -API_KEY = os.environ.get('API_KEY','xxx') -SECRET_KEY = os.environ.get('SECRET_KEY','xxx') - - - -client = AipOcr(APP_ID, API_KEY, SECRET_KEY) - -def ocr_image(image_data): - result = client.basicGeneral(image_data) - text = '' - if 'words_result' in result: - for item in result['words_result']: - text += item['words'] + '\n' - return text - -def process_pdf(file_path): - pdf = PdfReader(file_path) - num_pages = len(pdf.pages) - text = '' - for page_num in range(num_pages): - page = pdf.pages[page_num] - text += f'--------------------------------------------\n' - text += f'文档名:{os.path.basename(file_path)}\n' - text += f'页数:{page_num + 1}\n' - text += f'该页内容:\n' - text += page.extract_text() + '\n' - images = convert_from_path(file_path, first_page=page_num + 1, last_page=page_num + 1) - for image in images: - image_data = BytesIO() - image.save(image_data, format='PNG') - image_data = image_data.getvalue() - ocr_text = ocr_image(image_data) - if ocr_text: - text += f'图片文字:\n' - text += ocr_text + '\n' - text += '--------------------------------------------\n' - return text - -def process_doc(file_path): - doc = docx.Document(file_path) - text = '' - page_num = 1 - for paragraph in doc.paragraphs: - if paragraph.text.strip() == '': # 简单地将空行视为分页符 - page_num += 1 - else: - text += f'--------------------------------------------\n' - text += f'文档名:{os.path.basename(file_path)}\n' - text += f'页数:{page_num}\n' - text += f'该页内容:\n' - text += paragraph.text + '\n' - - for shape in doc.inline_shapes: - if shape.type == docx.enum.shape.WD_INLINE_SHAPE.PICTURE: - blip_id = shape._inline.graphic.graphicData.pic.blipFill.blip.embed - image_part = doc.part.related_parts[blip_id] - image_data = image_part.blob - ocr_text = ocr_image(image_data) - if ocr_text: - text += f'图片文字:\n' - text += ocr_text + '\n' - - return text - -def process_txt(file_path): - with open(file_path, 'r', encoding='utf-8') as f: - text = f.read() - return text - -def office_to_txt(file_path): - file_ext = os.path.splitext(file_path)[1].lower() - if file_ext == '.docx': - return process_doc(file_path) - elif file_ext == '.pdf': - return process_pdf(file_path) - elif file_ext == '.doc': - return process_doc(file_path) - elif file_ext == '.txt': - return process_txt(file_path) - - else: - raise ValueError('Unsupported file format') - diff --git a/python/api/test/fetch_test.py b/python/api/test/fetch_test.py deleted file mode 100644 index 86361b5e3fe6..000000000000 --- a/python/api/test/fetch_test.py +++ /dev/null @@ -1,25 +0,0 @@ -import requests - -# 接口的URL -api_url = "http://127.0.0.1:6010/generate_summary/" - -# 请求的数据 -data = { - "url": "https://bing.com", - "level": 1 -} - -# 发送POST请求 -response = requests.post(api_url, json=data) - -# 检查响应状态 -if response.status_code == 200: - # 请求成功,打印结果 - summaries = response.json() - for summary in summaries: - print(f"URL: {summary['url']}") - print(f"Title: {summary['title']}") - print(f"Summary: {summary['summary']}\n") -else: - # 请求失败,打印错误信息 - print(f"Failed to generate summary with status code {response.status_code}: {response.text}") diff --git a/python/api/test/office_test.py b/python/api/test/office_test.py deleted file mode 100644 index 1a7d67f156c1..000000000000 --- a/python/api/test/office_test.py +++ /dev/null @@ -1,49 +0,0 @@ -import requests -import pytest -from docx import Document -import os -from tempfile import NamedTemporaryFile -from docx.shared import Inches - -image_path = os.path.join(os.path.dirname(__file__), "test.png") - -# 定义一个函数来创建一个新的Word文档,并添加一个图片 -def create_test_docx_with_image(): - # 使用临时文件来避免文件名冲突 - temp_file = NamedTemporaryFile(delete=False, suffix='.docx') - # 创建一个文档对象 - doc = Document() - # 添加一个段落 - doc.add_paragraph("This is a test document with an image.") - # 添加一个图片,确保提供的图片路径是有效的 - doc.add_picture(image_path, width=Inches(1.25)) # 图片宽度设为1.25英寸 - # 保存文档到临时文件 - doc.save(temp_file.name) - # 关闭临时文件 - temp_file.close() - # 返回文件路径 - return temp_file.name - -# 定义一个函数,它将创建并发送多个Word文档,并返回响应对象列表 -def get_responses(): - responses = [] - # 创建并发送10个文档 - for _ in range(10): - test_file_path = create_test_docx_with_image() - with open(test_file_path, "rb") as f: - files = {"file": (os.path.basename(test_file_path), f, "application/vnd.openxmlformats-officedocument.wordprocessingml.document")} - response = requests.post("http://127.0.0.1:6010/extract_text/", files=files) - responses.append(response) - # 测试完成后删除文件 - os.unlink(test_file_path) - return responses - -# 使用pytest的parametrize装饰器测试所有响应 -@pytest.mark.parametrize("response", get_responses()) -def test_response(response): - # 断言响应的状态码为200 - assert response.status_code == 200 - # 断言响应的内容类型是application/json - assert "application/json" in response.headers["Content-Type"] - # 断言响应的数据包含文本信息 - assert "text" in response.json() diff --git a/scripts/postinstall.sh b/scripts/postinstall.sh index f332b8dc7a4e..6661aa20ed27 100755 --- a/scripts/postinstall.sh +++ b/scripts/postinstall.sh @@ -1,4 +1,2 @@ -# 创建临时文件目录 -mkdir -p projects/app/tmp # 初始化UI库的自定义ts类型 pnpm run gen:theme-typings diff --git a/test/datas/users.ts b/test/datas/users.ts new file mode 100644 index 000000000000..05002e0528b5 --- /dev/null +++ b/test/datas/users.ts @@ -0,0 +1,34 @@ +import { AuthUserTypeEnum } from '@fastgpt/global/support/permission/constant'; +import { MongoUser } from '@fastgpt/service/support/user/schema'; +import { MongoTeamMember } from '@fastgpt/service/support/user/team/teamMemberSchema'; +import { MongoTeam } from '@fastgpt/service/support/user/team/teamSchema'; +import { parseHeaderCertRet } from 'test/mocks/request'; + +export async function getRootUser(): Promise<parseHeaderCertRet> { + const rootUser = await MongoUser.create({ + username: 'root', + password: '123456' + }); + + const team = await MongoTeam.create({ + name: 'test team', + ownerId: rootUser._id + }); + + const tmb = await MongoTeamMember.create({ + teamId: team._id, + userId: rootUser._id, + status: 'active' + }); + + return { + userId: rootUser._id, + apikey: '', + appId: '', + authType: AuthUserTypeEnum.token, + isRoot: true, + sourceName: undefined, + teamId: tmb?.teamId, + tmbId: tmb?._id + }; +} diff --git a/test/mocks/index.ts b/test/mocks/index.ts new file mode 100644 index 000000000000..0e12cd8ceff7 --- /dev/null +++ b/test/mocks/index.ts @@ -0,0 +1 @@ +import './request'; diff --git a/test/mocks/request.ts b/test/mocks/request.ts new file mode 100644 index 000000000000..5a576018a0a3 --- /dev/null +++ b/test/mocks/request.ts @@ -0,0 +1,89 @@ +import { AuthUserTypeEnum } from '@fastgpt/global/support/permission/constant'; +import { vi } from 'vitest'; + +// vi.mock(import('@/service/middleware/entry'), async () => { +// const NextAPI = vi.fn((handler: any) => handler); +// return { +// NextAPI +// }; +// }); + +vi.mock(import('@fastgpt/service/common/middle/entry'), async (importOriginal) => { + const mod = await importOriginal(); + const NextEntry = vi.fn(({ beforeCallback = [] }: { beforeCallback?: Promise<any>[] }) => { + return (...args: any) => { + return async function api(req: any, res: any) { + try { + await Promise.all([...beforeCallback]); + let response = null; + for await (const handler of args) { + response = await handler(req, res); + if (res.writableFinished) { + break; + } + } + return { + code: 200, + data: response + }; + } catch (error) { + return { + code: 500, + error, + url: req.url + }; + } + }; + }; + }); + + return { + ...mod, + NextEntry + }; +}); + +export type parseHeaderCertRet = { + userId: string; + teamId: string; + tmbId: string; + appId: string; + authType: AuthUserTypeEnum; + sourceName: string | undefined; + apikey: string; + isRoot: boolean; +}; + +export type MockReqType<B = any, Q = any> = { + body?: B; + query?: Q; + auth?: parseHeaderCertRet; + [key: string]: any; +}; + +vi.mock(import('@fastgpt/service/support/permission/controller'), async (importOriginal) => { + const mod = await importOriginal(); + const parseHeaderCert = vi.fn( + ({ + req, + authToken = false, + authRoot = false, + authApiKey = false + }: { + req: MockReqType; + authToken?: boolean; + authRoot?: boolean; + authApiKey?: boolean; + }) => { + const { auth } = req; + if (!auth) { + return Promise.reject(Error('unAuthorization')); + } + return Promise.resolve(auth); + } + ); + return { + ...mod, + parseHeaderCert + }; +}); diff --git a/test/setup.ts b/test/setup.ts new file mode 100644 index 000000000000..471e4f5c142b --- /dev/null +++ b/test/setup.ts @@ -0,0 +1,86 @@ +import { readFileSync } from 'fs'; +import mongoose from '@fastgpt/service/common/mongo'; +import { connectMongo } from '@fastgpt/service/common/mongo/init'; +import { initGlobalVariables } from '@/service/common/system'; +import { afterAll, beforeAll, vi } from 'vitest'; +import { setup, teardown } from 'vitest-mongodb'; +import setupModels from './setupModels'; +import './mocks'; + +vi.stubEnv('NODE_ENV', 'test'); +vi.mock(import('@fastgpt/service/common/mongo'), async (importOriginal) => { + const mod = await importOriginal(); + return { + ...mod, + connectionMongo: await (async () => { + if (!global.mongodb) { + global.mongodb = mongoose; + await global.mongodb.connect((globalThis as any).__MONGO_URI__ as string); + } + + return global.mongodb; + })() + }; +}); + +vi.mock(import('@/service/common/system'), async (importOriginal) => { + const mod = await importOriginal(); + return { + ...mod, + getSystemVersion: async () => { + return '0.0.0'; + }, + readConfigData: async () => { + return readFileSync('@/data/config.json', 'utf-8'); + }, + initSystemConfig: async () => { + // read env from projects/app/.env + + const str = readFileSync('projects/app/.env.local', 'utf-8'); + const lines = str.split('\n'); + const systemEnv: Record<string, string> = {}; + for (const line of lines) { + const [key, value] = line.split('='); + if (key && value) { + systemEnv[key] = value; + } + } + global.systemEnv = systemEnv as any; + + return; + } + }; +}); + +beforeAll(async () => { + await setup({ + type: 'replSet', + serverOptions: { + replSet: { + count: 4 + } + } + }); + vi.stubEnv('MONGODB_URI', (globalThis as any).__MONGO_URI__); + initGlobalVariables(); + await connectMongo(); + + // await getInitConfig(); + const str = readFileSync('projects/app/.env.local', 'utf-8'); + const lines = str.split('\n'); + const systemEnv: Record<string, string> = {}; + for (const line of lines) { + const [key, value] = line.split('='); + if (key && value && !key.startsWith('#')) { + systemEnv[key] = value; + vi.stubEnv(key, value); + } + } + systemEnv.oneapiUrl = systemEnv['ONEAPI_URL']; + global.systemEnv = systemEnv as any; + await setupModels(); +}); + +afterAll(async () => { + await teardown(); +}); diff --git a/test/setupModels.ts b/test/setupModels.ts new file mode 100644 index 000000000000..4bc02f73beb6 --- /dev/null +++ b/test/setupModels.ts @@ -0,0 +1,52 @@ +import { ModelTypeEnum } from 'packages/global/core/ai/model'; +import { ModelProviderIdType } from 'packages/global/core/ai/provider'; + +export default async function setupModels() { + global.llmModelMap = new Map<string, any>(); + global.llmModelMap.set('gpt-4o-mini', { + type: ModelTypeEnum.llm, + model: 'gpt-4o-mini', + name: 'gpt-4o-mini', + avatar: 'gpt-4o-mini', + isActive: true, + isDefault: true, + isCustom: false, + requestUrl: undefined, + requestAuth: undefined, + customCQPrompt: '', + customExtractPrompt: '', + defaultSystemChatPrompt: undefined, + fieldMap: undefined, + defaultConfig: undefined, + provider: 'OpenAI' as ModelProviderIdType, + functionCall: false, + toolChoice: false, + maxContext: 4096, + maxResponse: 4096, + quoteMaxToken: 2048 + }); + global.systemDefaultModel = { + llm: { + type: ModelTypeEnum.llm, + model: 'gpt-4o-mini', + name: 'gpt-4o-mini', + avatar: 'gpt-4o-mini', + isActive: true, + isDefault: true, + isCustom: false, + requestUrl: undefined, + requestAuth: undefined, + customCQPrompt: '', + customExtractPrompt: '', + defaultSystemChatPrompt: undefined, + fieldMap: undefined, + defaultConfig: undefined, + provider: 'OpenAI' as ModelProviderIdType, + functionCall: false, + toolChoice: false, + maxContext: 4096, + maxResponse: 4096, + quoteMaxToken: 2048 + } + }; +} diff --git a/test/test.ts b/test/test.ts new file mode 100644 index 000000000000..57b67c4edb55 --- /dev/null +++ b/test/test.ts @@ -0,0 +1,18 @@ +import { MongoUser } from '@fastgpt/service/support/user/schema'; +import { it, expect } from 'vitest'; + +it('should be a test', async () => { + expect(1).toBe(1); +}); + +it('should be able to connect to mongo', async () => { + expect(global.mongodb).toBeDefined(); + expect(global.mongodb?.connection.readyState).toBe(1); + await MongoUser.create({ + username: 'test', + password: '123456' + }); + const user = await MongoUser.findOne({ username: 'test' }); + expect(user).toBeDefined(); + expect(user?.username).toBe('test'); +}); diff --git a/test/utils/request.ts b/test/utils/request.ts new file mode 100644 index 000000000000..6e94afa207df --- /dev/null +++ b/test/utils/request.ts @@ -0,0 +1,21 @@ +import { NextApiHandler } from '@fastgpt/service/common/middle/entry'; +import { MockReqType } from '../mocks/request'; + +export async function Call<B = any, Q = any, R = any>( + handler: NextApiHandler<R>, + props?: MockReqType<B, Q> +) { + const { body = {}, query = {}, ...rest } = props || {}; + return (await handler( + { + body: body, + query: query, + ...(rest as any) + }, + {} as any + )) as Promise<{ + code: number; + data: R; + error?: any; + }>; +} diff --git a/tsconfig.json b/tsconfig.json index 092946c2dd49..9b4dcca57d84 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -14,7 +14,12 @@ "isolatedModules": true, "jsx": "preserve", "incremental": true, - "baseUrl": "." + "baseUrl": ".", + "paths": { + "@/*": ["projects/app/src/*"], + "@fastgpt/*": ["packages/*"], + "@test": ["test/*"] + } }, "exclude": ["**/node_modules"] } diff --git a/vitest.config.mts b/vitest.config.mts new file mode 100644 index 000000000000..6f7ba043dfad --- /dev/null +++ b/vitest.config.mts @@ -0,0 +1,22 @@ +import { resolve } from 'path'; +import { defineConfig } from 'vitest/config'; +export default defineConfig({ + test: { + coverage: { + enabled: true, + reporter: ['text', 'json', 'html'], + all: false + }, + outputFile: 'test-results.json', + setupFiles: ['./test/setup.ts'], + include: ['./test/test.ts', './projects/app/**/*.test.ts'], + testTimeout: 5000 + }, + resolve: { + alias: { + '@': resolve('projects/app/src'), + '@fastgpt': resolve('packages'), + '@test': resolve('test') + } + } +});